CESI: Architecture N Tiers, exemple microservices
This commit is contained in:
parent
8d6f44d938
commit
b975949318
1
cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/.gitignore
vendored
Normal file
1
cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
gateway:
|
||||||
|
build:
|
||||||
|
context: containers/gateway
|
||||||
|
ports:
|
||||||
|
- 8080:80
|
||||||
|
|
||||||
|
home_page:
|
||||||
|
build:
|
||||||
|
context: containers/home_page
|
||||||
|
login_page:
|
||||||
|
build:
|
||||||
|
context: containers/login_page
|
|
@ -0,0 +1,8 @@
|
||||||
|
FROM alpine:3.2
|
||||||
|
|
||||||
|
RUN apk add --no-cache nginx
|
||||||
|
COPY microbloggr.conf /etc/nginx/microbloggr.conf
|
||||||
|
|
||||||
|
RUN mkdir -p /var/run/nginx
|
||||||
|
|
||||||
|
CMD ["nginx", "-c", "/etc/nginx/microbloggr.conf"]
|
|
@ -0,0 +1,45 @@
|
||||||
|
daemon off;
|
||||||
|
|
||||||
|
worker_processes 1;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
|
||||||
|
server {
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://home_page:8080/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /login {
|
||||||
|
proxy_pass http://login_page:8080/;
|
||||||
|
}
|
||||||
|
|
||||||
|
# location /profile {
|
||||||
|
# proxy_pass http://profile_page:8080/;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# location /register {
|
||||||
|
# proxy_pass http://register_page:8080/;
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# location /statuses {
|
||||||
|
# proxy_pass http://statuses_page:8080/;
|
||||||
|
# }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
/app/node_modules
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM alpine:3.7
|
||||||
|
|
||||||
|
RUN apk add --no-cache nodejs
|
||||||
|
|
||||||
|
COPY app /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
|
@ -0,0 +1 @@
|
||||||
|
/node_modules
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "login_page",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"ejs": "^2.5.7",
|
||||||
|
"express": "^4.16.2",
|
||||||
|
"express-session": "^1.15.6",
|
||||||
|
"request": "^2.83.0",
|
||||||
|
"request-promise-native": "^1.0.5"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
const express = require('express');
|
||||||
|
const session = require('express-session');
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
|
||||||
|
app.use(session({
|
||||||
|
secret: 'AbsolutlyNotSecret', // Ce secret doit être partagé par tous les microservices *_page
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// GET /
|
||||||
|
// Retourne la page d'accueil de MicroBloggr ou redirige vers la
|
||||||
|
// page de login si l'utilisateur n'est pas identifié
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
|
||||||
|
const sess = req.session;
|
||||||
|
|
||||||
|
// On vérifie si un utilisateur a été associé à la session HTTP
|
||||||
|
if (req.session.user) {
|
||||||
|
// Si oui, on affichage la page d'accueil
|
||||||
|
return res.render('index');
|
||||||
|
} else {
|
||||||
|
// Si non, on redirige l'utilisateur vers la page de login
|
||||||
|
return res.redirect(301, '/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(8080, () => console.log('listening on port 8080'));
|
|
@ -0,0 +1,9 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div class="layout">
|
||||||
|
<nav class="main">
|
||||||
|
<h1 class="brand">MicroBloggr</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
/app/node_modules
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM alpine:3.7
|
||||||
|
|
||||||
|
RUN apk add --no-cache nodejs
|
||||||
|
|
||||||
|
COPY app /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
|
@ -0,0 +1 @@
|
||||||
|
/node_modules
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "login_page",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.18.2",
|
||||||
|
"ejs": "^2.5.7",
|
||||||
|
"express": "^4.16.2",
|
||||||
|
"express-session": "^1.15.6",
|
||||||
|
"request": "^2.83.0",
|
||||||
|
"request-promise-native": "^1.0.5"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
const express = require('express');
|
||||||
|
const app = express();
|
||||||
|
const session = require('express-session');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const request = require('request-promise-native');
|
||||||
|
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
|
||||||
|
app.use(session({
|
||||||
|
secret: 'AbsolutlyNotSecret', // Ce secret doit être partagé par tous les microservices *_page
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// On utilise le module "body-parser"
|
||||||
|
// pour faciliter la désérialisation des données
|
||||||
|
// du formulaire
|
||||||
|
app.use(bodyParser.urlencoded({extended: false}));
|
||||||
|
|
||||||
|
// GET /
|
||||||
|
// Affiche la page de login
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.render('index', { error: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// POST /
|
||||||
|
// Récupère les données du formulaire de login
|
||||||
|
app.post('/', (req, res) => {
|
||||||
|
|
||||||
|
const form = req.body;
|
||||||
|
|
||||||
|
// Si le formulaire ne contient pas les données attendues
|
||||||
|
// on renvoie un code HTTP 400 pour indiquer une requête invalide.
|
||||||
|
if(!form.username || !form.password) {
|
||||||
|
return res.render('index', { error: 'Identifiant ou mot de passe manquant.'});
|
||||||
|
}
|
||||||
|
|
||||||
|
// On interroge le microservices "users"
|
||||||
|
// pour récupérer l'utilisateur associé à l'identifiant
|
||||||
|
// fourni dans le formulaire
|
||||||
|
findUserByName(form.username)
|
||||||
|
.then(user => {
|
||||||
|
|
||||||
|
// Si l'utilisateur n'existe pas, on affiche un message d'erreur
|
||||||
|
if(!user) {
|
||||||
|
return res.render('index', { error: 'Identifiant invalide !'});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si le mot de passe de l'utilisateur correspond à celui enregistré sur le
|
||||||
|
// microservice "users", on attache l'identifiant de l'utilisateur à la session
|
||||||
|
// HTTP puis on le redirige sur la page d'accueil
|
||||||
|
if(user.password === form.password) {
|
||||||
|
req.session.user = user;
|
||||||
|
return res.redirect(302, '/');
|
||||||
|
} else {
|
||||||
|
// Sinon, on affiche un message d'erreur
|
||||||
|
return res.render('index', { error: 'Mot de passe invalide !'});
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// Une erreur s'est produite en essayant de contacter le microservices "users"
|
||||||
|
// On affiche un message d'erreur à l'utilisateur
|
||||||
|
return res.render('index', {
|
||||||
|
error: 'Une erreur s\'est produite pendant le processus d\'authentification.'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(8080, () => console.log('listening on port 8080'));
|
||||||
|
|
||||||
|
// Récupère un utilisateur via son identifiant
|
||||||
|
// sur le microservice "users"
|
||||||
|
function findUserByName(username) {
|
||||||
|
return request({
|
||||||
|
uri: `http://users:8080/users/${username}`,
|
||||||
|
json: true
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// Si le microservice "users" répond avec un code HTTP 404
|
||||||
|
// c'est que l'utilisateur n'existe pas
|
||||||
|
if (err.response.statusCode === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Login</h1>
|
||||||
|
<% if (error) { %>
|
||||||
|
<div class="error"><p><%= error %></p></div>
|
||||||
|
<% } %>
|
||||||
|
<form method="POST" enctype="application/x-www-form-urlencoded">
|
||||||
|
<div>
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" name="username" id="username" required />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="username">Password</label>
|
||||||
|
<input type="password" name="password" id="password" required />
|
||||||
|
</div>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
/app/node_modules
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM alpine:3.7
|
||||||
|
|
||||||
|
RUN apk add --no-cache nodejs
|
||||||
|
|
||||||
|
COPY app /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "sessions",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.18.2",
|
||||||
|
"express": "^4.16.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
const express = require('express');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const sessions = {};
|
||||||
|
let sessionCounter = 0;
|
||||||
|
|
||||||
|
// On utilise le middleware "body-parser"
|
||||||
|
// pour désérialiser automatiquement
|
||||||
|
// les objets JSON potentiellement
|
||||||
|
// fournis par les requêtes
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
// GET /sessions
|
||||||
|
// Permet de récupérer la liste des sessions en cours
|
||||||
|
app.get('/sessions', (req, res) => {
|
||||||
|
res.json(sessions);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// GET /sessions/:id
|
||||||
|
// Permet de récupérer une session via son identifiant
|
||||||
|
app.get('/sessions/:id', (req, res) => {
|
||||||
|
|
||||||
|
// On récupère l'identifiant de session
|
||||||
|
// depuis l'URL
|
||||||
|
const sessionId = req.params.id;
|
||||||
|
|
||||||
|
// On vérifie que la session existe.
|
||||||
|
// Si ce n'est pas le cas, on retourne un code
|
||||||
|
// HTTP 404 pour indiquer que la ressource
|
||||||
|
// n'existe pas
|
||||||
|
if (!sessions.hasOwnProperty(sessionId)) {
|
||||||
|
return res.status(404).end('Not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// On retourne la session sérialisée en JSON
|
||||||
|
return res.json(sessions[sessionId]);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// POST /sessions
|
||||||
|
// Body: {"user": <user_id> }
|
||||||
|
// Permet de créer une nouvelle session pour l'utilisateur donné
|
||||||
|
app.post('/sessions', (req, res) => {
|
||||||
|
|
||||||
|
const payload = req.body;
|
||||||
|
|
||||||
|
// On valide la présence de l'attribut "user"
|
||||||
|
// sur les données fournies.
|
||||||
|
// Si il n'est pas présent, on retourne un code HTTP
|
||||||
|
// 400 pour indiquer que la requête est invalide.
|
||||||
|
if (!payload.user) {
|
||||||
|
return res.status(400).end('Bad request.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// On créait une nouvelle session
|
||||||
|
const sess = {
|
||||||
|
user: payload.user,
|
||||||
|
id: getSessionId(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// On la "stocke" dans notre registre
|
||||||
|
sessions[session.id] = sess;
|
||||||
|
|
||||||
|
// On retourne la session
|
||||||
|
// nouvellement créée
|
||||||
|
res.json(sess);
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// DELETE /sessions/:id
|
||||||
|
// Permet de supprimer une session dans le registre via son identifiant
|
||||||
|
app.delete('/sessions/:id', (req, res) => {
|
||||||
|
|
||||||
|
// On récupère l'identifiant de session
|
||||||
|
// depuis l'URL
|
||||||
|
const sessionId = req.params.id;
|
||||||
|
|
||||||
|
// On vérifie que la session existe.
|
||||||
|
// Si ce n'est pas le cas, on retourne un code
|
||||||
|
// HTTP 404 pour indiquer que la ressource
|
||||||
|
// n'existe pas
|
||||||
|
if (!sessions.hasOwnProperty(sessionId)) {
|
||||||
|
return res.status(404).end('Not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
delete sessions[sessionId];
|
||||||
|
|
||||||
|
// On retourne un code HTTP 204 pour indiquer que l'opération
|
||||||
|
// s'est déroulée correctement mais qu'aucun "contenu" n'est disponible
|
||||||
|
return res.status(204);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.listen(8080, () => console.log('listening on port 8080'));
|
||||||
|
|
||||||
|
function getSessionId() {
|
||||||
|
return `${Date.now()}-${sessionCounter++}`;
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
Binary file not shown.
Loading…
Reference in New Issue