- Клиент-серверная архитектура
- HTTP
- API
- Самописный API 1.
- Самописный API 2.
- соединяем всё вместе
- практика Microservices
- Почитать
- Вопросы?
Клиент-серверная архитектура
from django.http import HttpResponse
def index(request):
return HttpResponse("<html><body>Привет</body></html>") # -> str
# в чём отличия от такого return?
return "<html><body>Привет</body></html>"
HTTP
curl -v http://example.com/index.html 2>&1
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 2606:2800:220:1:248:1893:25c8:1946:80...
* TCP_NODELAY set
* Connected to example.com (2606:2800:220:1:248:1893:25c8:1946) port 80 (#0)
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0> GET /index.html HTTP/1.1
> Host: example.com
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Age: 304182
< Cache-Control: max-age=604800
< Content-Type: text/html; charset=UTF-8
< Date: Mon, 16 Jan 2023 11:05:55 GMT
< Etag: "3147526947+gzip"
< Expires: Mon, 23 Jan 2023 11:05:55 GMT
< Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
< Server: ECS (nyb/1D10)
< Vary: Accept-Encoding
< X-Cache: HIT
< Content-Length: 1256
<
{ [1256 bytes data]
100 1256 100 1256 0 0 4257 0 --:--:-- --:--:-- --:--:-- 4243
* Connection #0 to host example.com left intact
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
API
HTTP (WEB) API — Application Programming Interface
JSON – JavaScript Object Notation
[…] {}, True = true, False = false, None = null
YAML – Yet Another Markup Language
XML – eXtended Markup Language
BJSON (binary)
ProtoBuf (binary)
import requests
# API функции get
requests.get(...)
- GET myapi.ru/json/?search=foobar
- GET myapi.ru/xml/?search=foobar
- GET myapi.ru/?search=foobar&format=json
I
curl https://svatky.adresa.info/json |jq
[
{
"date": "1601",
"name": "Ctirad"
}
]
curl https://svatky.adresa.info/xml
<?xml version="1.0" encoding="UTF-8"?><svatky><svatek><date>1601</date><name>Ctirad</name></svatek></svatky>
II
curl https://svatky.adresa.info/json?=0910 | jq
[
{
"date": "1601",
"name": "Ctirad"
}
]
III
curl --output - https://http.cat/200
curl --output - https://http.cat/404
curl --output - https://http.cat/502
curl --output - https://http.cat/418
Самописный API 1.
import socket
HOST = '127.0.0.1' # Standard loopback interface address (localhost)
PORT = 9010 # Port to listen on (non-privileged ports are > 1023)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
import socket
HOST = '127.0.0.1'
PORT = 9020
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024).decode('utf-8')
if not data:
break
try:
path = data.rstrip('\n').rstrip('\r')
print(repr(path))
with open(path, 'r') as fd:
conn.sendall((fd.read(1000) + '\r\n').encode('utf-8'))
except Exception as e:
conn.sendall(str(e + '\r\n').encode('utf-8'))
except Exception as e:
print(e)
finally:
s.shutdown(socket.SHUT_RDWR)
import io
import json
import http.server
import socketserver
from http import HTTPStatus
from datetime import datetime as dt
class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
now = dt.now()
data = json.dumps({
"dt": now.strftime("%Y%m%d:%T"),
"ts": int(now.timestamp())
})
s_data = io.BytesIO()
s_data.write(data.encode("utf-8"))
s_data.seek(0)
self.send_response(HTTPStatus.OK)
self.send_header("Content-type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.copyfile(s_data, self.wfile)
my_server = socketserver.TCPServer(("", 9020), MyHttpRequestHandler)
# Star the server
my_server.serve_forever()
Самописный API 2.
соединяем всё вместе
<!DOCTYPE html>
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.6.1.min.js"
integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ="
crossorigin="anonymous"></script>
<script src="spa.js"></script>
</head>
<body>
<div id="content">
</div>
</body>
</html>
var send_log = function (level, message) {
$.post("http://localhost:8000/api/log/", {level: level, message: message})
.done( function (result) {
console.log("SUCCESS: " + result);
})
.fail( function (result) {
alert("FAIL: " + result);
});
};
var read_notifications = function () {
var ts_url = "http://localhost:9020";
$.get(ts_url).done(function (result) {
send_log("DBG", "Got current date data: " + result);
$.get("http://localhost:8000/api/notify/", {"ts": result["ts"]})
.done(function (notifications) {
send_log("got " + notifications.length + " notifications");
console.log(notifications);
});
}).fail(function (result) {send_log("ERR", result);});
}
$(document).ready(function () {
var timerId = setInterval(read_notifications, 3000);
});
практика Microservices
server {
listen 80;
server_name localhost 127.0.0.1;
location / {
root /home/pimiento/yap/API_webinar;
index index.html;
}
location /api/ts/ {
proxy_pass http://localhost:9020;
}
location /api/ {
proxy_pass http://localhost:8000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
sudo cp webinar.conf /etc/nginx/conf.d/
sudo nginx -t 2>&1
sudo nginx -s reload 2>&1
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
var send_log = function (level, message) {
$.post("/api/log/", {level: level, message: message})
.done( function (result) {
console.log("SUCCESS: " + result);
})
.fail( function (result) {
console.log(result);
alert("FAIL: send_log");
});
};
var read_notifications = function () {
$.get("/api/ts/", function (result) {
send_log("DBG", "Got current date data: " + $.param(result));
$.get("/api/notify/", {"ts": result["ts"]})
.done(function (notifications) {
send_log("INF", "got " + notifications.length + " notifications");
console.log(notifications);
});
});
}
$(document).ready(function () {
var timerId = setInterval(read_notifications, 800);
});
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8"/>
<script
src="https://code.jquery.com/jquery-3.6.1.min.js"
integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ="
crossorigin="anonymous"></script>
<script src="spa.js"></script>
</head>
<body>
<div id="content">
<form id="set-notify-form" accept-charset="utf-8">
<div>
<label for="id_sec">seconds later:</label>
<input type="number" name="sec" required id="id_sec" min=1 max=86400>
</div>
<div>
<label for="id_message">Message:</label>
<textarea name="message" cols="40" rows="10" required id="id_message"></textarea>
</div>
<input type="submit"/>
</form>
<br/><br/>
<div id="notifications">
</div>
</div>
</body>
</html>
var send_log = function (level, message) {
$.post("/api/log/", {level: level, message: message})
.done( function (result) {
console.log("SUCCESS: " + result);
})
.fail( function (result) {
console.log(result);
alert("FAIL: send_log");
});
};
var set_notify = function (delta, message) {
// delta — activate notification delta seconds later
// message — just a text
var when = new Date();
when.setSeconds(when.getSeconds() + delta);
$.post("/api/notify/", {ts: when.toISOString(), message: message})
.done( function () {
send_log("INF", "set notification: " + when + " -- " + message);
})
.fail( function (result) {
alert("FAIL: " + result);
send_log("ERR", result);
});
}
var read_notifications = function () {
$.get("/api/ts/", function (result) {
send_log("DBG", "Got current date data: " + $.param(result));
$.get("/api/notify/", {"ts": result["ts"]})
.done(function (notifications) {
send_log("INF", "got " + notifications.length + " notifications");
let container = $("#notifications");
container.empty();
for (const note in notifications) {
container.append(
'<p class="notification">'
+ '<span class="notification_ts">'
+ notifications[note]["ts"]
+ ': </span><span class="notification_message">'
+ notifications[note]["message"]
+ '</span></p>'
);
}
});
});
}
$(document).ready(function () {
var timerId = setInterval(read_notifications, 3000);
$("#set-notify-form").submit(function (e) {
let data, form;
e.preventDefault();
form = $(this);
data = form.serializeArray().reduce(function (obj, item) {
obj[item.name] = item.value;
return obj;
}, {});
this.reset();
set_notify(data["sec"], data["message"]);
});
});
API1
FROM python:3.8
WORKDIR /app
COPY my_django2.py .
CMD python my_django2.py
API2
FROM python:3.8
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
RUN rm -f db.sqlite3
RUN python manage.py migrate
CMD python manage.py runserver
version: '3.8'
services:
nginx:
image: nginx:1.19.3
ports:
- 80:80
volumes:
- ./microservices.conf:/etc/nginx/conf.d/default.conf
- ./index.html:/var/html/index.html
- ./spa.js:/var/html/spa.js
restart: always
depends_on:
- api1
- api2
api1:
build:
context: .
dockerfile: Dockerfile
api2:
build:
context: foobar
dockerfile: Dockerfile
server {
listen 80;
# это будет дефолтный, не важно что написано в server_name, он единственный
location / {
root /var/html;
index index.html;
}
location /api/ts/ {
proxy_pass http://api1:9020;
}
location /api/ {
proxy_pass http://api2:8000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}