CESI: Architecture N Tiers, exemple microservices

This commit is contained in:
wpetit 2018-01-22 22:14:30 +01:00 committed by Benjamin Bohard
parent 8d6f44d938
commit b975949318
26 changed files with 407 additions and 0 deletions

View File

@ -0,0 +1 @@
node_modules

View File

@ -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

View File

@ -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"]

View File

@ -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/;
# }
}
}

View File

@ -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"]

View File

@ -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"
}
}

View File

@ -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'));

View File

@ -0,0 +1,9 @@
<html>
<body>
<div class="layout">
<nav class="main">
<h1 class="brand">MicroBloggr</div>
</nav>
</div>
</body>
</html>

View File

@ -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"]

View File

@ -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"
}
}

View File

@ -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;
})
;
}

View File

@ -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>

View File

@ -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"]

View File

@ -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"
}
}

View File

@ -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.

After

Width:  |  Height:  |  Size: 48 KiB