Nhớ lại, trong phần một tôi hỏi bạn một câu hỏi: “Làm thế nào để bạn chạy ứng dụng Django, và ứng dụng Flask và ứng dụng Pyramid trên một máy chủ web mà không thực hiện một thay đổi nào cho máy chủ đáp ứng đầy đủ các khuôn khổ web khách nhau?”. Đọc tiếp để tìm câu trả lời.

 

Trong quá khứ, bạn chọn một Python Web Framework có thể bị giới hạn về nhưng máy chủ web có thể dùng được, và ngược lại. Nếu Framework và máy chủ được thiết kế để làm việc cùng nhau, thì bạn vẫn ổn:

Nhưng bạn có thể phải đối mặt với (và có thể bạn đã) các vấn đề khi cố gắng kết hợp Framework với Server mà không được thiết kế để làm việc cùng nhau.

Về cơ bản, bạn cần sử dụng cái gì đó để chúng làm việc với nhau, chứ không phải bạn muốn là sử dụng.

Vậy, làm thế nào bạn chắc chắn có thể chạy một máy chủ web với nhiều Web Framework khác nhau mà không làm thay đổi máy chủ web hoặc web Framework? Và để trả lời cho vấn đề đó đã trở thành Python Web Server Gateway Interface (or WSGI for short, pronounced “wizgy”).

WSGI cho phép lập trình viên lựa chọn một Web Framework từ Web Server. Bây giờ bạn có thể trộn và kết hợp Web Servers và Web Frameworks và chọn một kiểu ghép nối phù hợp với nhu cầu của bạn. Bạn cần chạy Django, Flask hoặc Pyramid, ví dụ: với Gunicorn hoặc Nginx/uWSGI hoặc Waitress. Trộn và kết hợp đồng thời, nhờ sự hỗ trợ WSGI trong Server và Framework.

 

Vậy, WSGI là câu trả lời cho câu hỏi của Tôi ở phần một và lặp lại ở đầu bài viết này. Web Server phải triển khai phần máy chủ của giao diện WSGI, và tất cả Python Web Frameworks đã được triển khai ở giao diện của WSGI, cái cho phép bạn sử dụng với Web Server không bao giờ sửa đổi Web Server để phù hợp với Web Framework.

 

Giờ bạn đã biết Web Server hỗ trợ WSGI và Web Framework cho phép bạn chọn một ghép nối phù hợp, nhưng nó cũng có lợi cho các nhà phát triển bởi vì chúng có thể tập chung vào một lĩnh vực mà không tập trung vào nhưng nhánh khác. Các ngôn ngữ khác cũng có giao diện tương tự: Java có Servlet API và Ruby has Rack.

 

Tất cả đều tốt. Nhưng tôi cá là bạn đang nói: “Cho tôi xem code!”. Okey,  nhìn vào máy chủ WSGI khá rõ ràng nè.

# Tested with Python 2.7.9, Linux & Mac OS X
import socket
import StringIO
import sys

class WSGIServer(object):
   address_family = socket.AF_INET
   socket_type = socket.SOCK_STREAM
   request_queue_size = 1
 
   def __init__(self, server_address):
       # Create a listening socket
       self.listen_socket = listen_socket = socket.socket(
           self.address_family,
           self.socket_type
       )

       # Allow to reuse the same address
       listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
       # Bind
       listen_socket.bind(server_address)
       # Activate
       listen_socket.listen(self.request_queue_size)
       # Get server host name and port
       host, port = self.listen_socket.getsockname()[:2]
       self.server_name = socket.getfqdn(host)
       self.server_port = port
       # Return headers set by Web framework/Web application
       self.headers_set = []
 
   def set_app(self, application):
       self.application = application
   def serve_forever(self):
       listen_socket = self.listen_socket
       while True:
           # New client connection
           self.client_connection, client_address = listen_socket.accept()
           # Handle one request and close the client connection. Then
           # loop over to wait for another client connection
           self.handle_one_request()
 
   def handle_one_request(self):
       self.request_data = request_data = self.client_connection.recv(1024)
       # Print formatted request data a la 'curl -v'
       print(''.join(
           '< {line}\n'.format(line=line)
           for line in request_data.splitlines()
       ))
 
       self.parse_request(request_data)
       # Construct environment dictionary using request data
       env = self.get_environ()
       # It's time to call our application callable and get
       # back a result that will become HTTP response body
       result = self.application(env, self.start_response)
       # Construct a response and send it back to the client
       self.finish_response(result)
 
   def parse_request(self, text):
       request_line = text.splitlines()[0]
       request_line = request_line.rstrip('\r\n')
       # Break down the request line into components
       (self.request_method,  # GET
        self.path,            # /hello
        self.request_version  # HTTP/1.1
        ) = request_line.split()
 
   def get_environ(self):
       env = {}
       # The following code snippet does not follow PEP8 conventions
       # but it's formatted the way it is for demonstration purposes
       # to emphasize the required variables and their values
       #
       # Required WSGI variables
       env['wsgi.version']      = (1, 0)
       env['wsgi.url_scheme']   = 'http'
       env['wsgi.input']        = StringIO.StringIO(self.request_data)
       env['wsgi.errors']       = sys.stderr
       env['wsgi.multithread']  = False
       env['wsgi.multiprocess'] = False
       env['wsgi.run_once']     = False

       # Required CGI variables
       env['REQUEST_METHOD']    = self.request_method # GET
       env['PATH_INFO']         = self.path # /hello
       env['SERVER_NAME']       = self.server_name # localhost
       env['SERVER_PORT']       = str(self.server_port) # 8888
       return env
 
   def start_response(self, status, response_headers, exc_info=None):
       # Add necessary server headers
       server_headers = [
           ('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),
           ('Server', 'WSGIServer 0.2'),
       ]

       self.headers_set = [status, response_headers + server_headers]
       # To adhere to WSGI specification the start_response must return
       # a 'write' callable. We simplicity's sake we'll ignore that detail
       # for now.
       # return self.finish_response
 
   def finish_response(self, result):
       try:
           status, response_headers = self.headers_set
           response = 'HTTP/1.1 {status}\r\n'.format(status=status)
           for header in response_headers:
               response += '{0}: {1}\r\n'.format(*header)
           response += '\r\n'
           for data in result:
               response += data
           # Print formatted response data a la 'curl -v'
           print(''.join(
               '> {line}\n'.format(line=line)
               for line in response.splitlines()
           ))
           self.client_connection.sendall(response)
       finally:
           self.client_connection.close()

SERVER_ADDRESS = (HOST, PORT) = '', 8888
 
def make_server(server_address, application):
   server = WSGIServer(server_address)
   server.set_app(application)
   return server
 
if __name__ == '__main__':
   if len(sys.argv) < 2:
       sys.exit('Provide a WSGI application object as module:callable')

   app_path = sys.argv[1]
   module, application = app_path.split(':')
   module = __import__(module)
   application = getattr(module, application)
   httpd = make_server(SERVER_ADDRESS, application)
   print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))
   httpd.serve_forever()
 
Nó chắc chắn sẽ to hơn máy chủ code trong phần 1, nhưng nó cũng vừa đủ (dưới 150 dòng) cho bạn hiểu mà không cần hiểu quá chi tiết. Máy chủ phía trên cũng làm được nhiều hơn - nó cũng có thể chạy ứng dụng web cơ bản của bạn được viết bằng một web framework, như là Pyramid, Flask, Django hoặc một vài Python WSGI framework khác.

Không tin tôi? Thử nó và tự bạn kiểm chứng. Lưu code phí trên với tên webserver2.py hoặc tải nó ở link github: https://github.com/rspivak/lsbaws/blob/master/part2/webserver2.py . Nếu bạn thử và chạy nó không có bất kỳ tham số nó sẽ biên dịch và thoát.

$ python webserver2.py
Provide a WSGI application object as module:callable
 

Nó thực sự muốn phục vụ ứng dụng web của bạn và đó là nơi vui vẻ bắt đầu. Để chạy máy chủ, bạn cần cài đặt Python. Nhưng để chạy được ứng dụng được viết bằng Pyramid, Flask, and Django bạn cần cài đặt nhưng framework này trước. Hãy cài đặt cả ba. Phương pháp của tôi là sử dụng virtualenv. Chỉ cần làm theo các bước dưới đây để tạo và kích hoạt môi trường ảo sau đó cài đặt ba web framework.

$ [sudo] pip install virtualenv
$ mkdir ~/envs
$ virtualenv ~/envs/lsbaws/
$ cd ~/envs/lsbaws/
$ ls
bin  include  lib
$ source bin/activate
(lsbaws) $ pip install pyramid
(lsbaws) $ pip install flask
(lsbaws) $ pip install django

 

Tại thời điểm này, bạn cần tạo một ứng dụng web. Hãy bắt đầu với Pyramid. Lưu code dưới đây với tên pyramidapp.py vào cùng thư mục mà bạn lưu webserver2.py hoặc download từ GitHub.

from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
    return Response(
        'Hello world from Pyramid!\n',
        content_type='text/plain',
    )

config = Configurator()
config.add_route('hello', '/hello')
config.add_view(hello_world, route_name='hello')
app = config.make_wsgi_app()

 

Bạn đã sẵn sàng để chạy ứng dụng Pyramid với máy chủ web của riêng bạn.

(lsbaws) $ python webserver2.py pyramidapp:app
WSGIServer: Serving HTTP on port 8888 ...

 

Bạn vừa yêu cầu máy chủ của bạn tải ‘ứng dụng’ từ module ‘pyramidapp’. Server của bạn đã sẵn sàng để gửi yêu cầu và chuyển tiếp chúng đến ứng dụng Pyramid của bạn. Ứng dụng này xử lý một điều hướng: điều hướng /hello. Nhập http://localhost:8888/hello trên thanh địa chỉ của trình duyệt, nhấn Enter, và quan sát kết quả:

 

Bạn cũng có thể kiểm tra trên command line bằng cách sử dụng lệnh curl.

$ curl -v http://localhost:8888/hello
...

 

Kiểm tra máy chủ và lệnh curl in ra như trên.

Bây giờ làm trên Flask và làm theo mẫu sau.

from flask import Flask
from flask import Response
flask_app = Flask('flaskapp')

@flask_app.route('/hello')
def hello_world():
    return Response(
        'Hello world from Flask!\n',
        mimetype='text/plain'
    )

app = flask_app.wsgi_app

 

Lưu code với tên flaskapp.py và chạy nó:

 
(lsbaws) $ python webserver2.py flaskapp:app
WSGIServer: Serving HTTP on port 8888 ...

 

Nhập http://localhost:8888/hello vào thanh địa chỉ trình duyệt và nhấn Enter.

 

Một lần nữa, thử lại ‘curl' và máy chủ trả về một thông báo tạo bởi ứng dụng Flask.

$ curl -v http://localhost:8888/hello
...

 

Máy chủ có thể xử lý một ứng dụng Django không? Hay thử nó! Tuy nhiên đó là một chút liên quan, tôi khuyên bạn nên nhân bản repo và sử dụng djangoapp.py, cái là một phần của GitHub. Đây là mã nguồn cơ bản của dự án ‘helloworld' bằng Django ( được tạo trước bằng lệnh chạy django-admin.py của project ) đường dẫn hiện tại của Python và sau đó nhập project của dự án WSGI.

 
import sys
sys.path.insert(0, './helloworld')
from helloworld import wsgi

app = wsgi.application

 

Lưu mã trên với tên djangoapp.py và chạy ứng dụng Django bằng máy chủ Web của bạn.

(lsbaws) $ python webserver2.py djangoapp:app
WSGIServer: Serving HTTP on port 8888 ...

Nhập trên thanh địa chỉ:

 

Giống như bạn thực hiện một vài lần trước, bạn cũng có thể kiểm tra nó trên command line và xác nhận ứng dụng Django xử lý các yêu cầu của bạn lần này!

$ curl -v http://localhost:8888/hello
...

 

Bạn thử chưa? Bạn có chắc chắn rằng máy chủ hoạt động với cả 3 framework? Nếu không, hay làm như vậy. Đọc là quan trọng, nhưng đây là chuỗi bài được xây dựng lại và nó nghĩ là bạn cần hiểu sâu hơn. Hãy thử nó, tôi sẽ đợi bạn, đừng lo lắng. Không nghiêm túc, bạn phải thử nó và có thể tốt hơn, tự mình nhập lại mọi thứ và đảm bảo nó hoạt động như mong đợi.

Okey, Bạn đã có kinh nghiệm về WSGI: nó cho phép bạn trộn và kết hợp nhiều Web Server và nhiều Web Framework. WSGI cung cấp một giao diện giữa Python Web Server và Python Web Framework. Nó rất đơn giản và dễ dàng triển khai trên cả hai máy chủ và framework. Đoạn code sau thể hiện một phần nhỏ server và framework phía giao diện.

Khóa học lập trình Web tại Techmaster học qua dự án, thực tập trực tiếp tại trung tâm.

def run_application(application):
    """Server code."""
    # This is where an application/framework stores
    # an HTTP status and HTTP response headers for the server
    # to transmit to the client
    headers_set = []
    # Environment dictionary with WSGI/CGI variables
    environ = {}

    def start_response(status, response_headers, exc_info=None):
        headers_set[:] = [status, response_headers]

    # Server invokes the ‘application' callable and gets back the
    # response body
    result = application(environ, start_response)
    # Server builds an HTTP response and transmits it to the client
    …

def app(environ, start_response):
    """A barebones WSGI app."""
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['Hello world!']

run_application(app)

 

Đây là cách nó làm việc:

1. Framework cung cấp một ứng dụng (WSGI không quy định cách thực hiện điều đó).

2. Server gọi ‘application' cho mỗi yêu cầu nó nhận từ phía client qua giao thức HTTP. Nó chuyển một từ điển ‘environ’ chứa các biến WSGI/CGI và một ‘start_response’ đối số cho ứng dụng được gọi.

3. Framework/ứng dụng tạo ra một trạng thái HTTP và header của HTTP phản hồi và chuyển chúng đến ‘start_response’ được gọi cho server lưu trữ chúng. Framework/ứng dụng cũng trả về thân phản hồi.

4. Server kết hợp trạng thái, header phản hồi, và thân phản hồi trong một phản hồi HTTP và chuyển giao nó tới client (Bước này là không rõ ràng nhưng nó là bước tiếp theo trong luồng và tôi thêm nó để làm rõ.

Và đây là một biểu diễ trực quan:

 

Bạn đã thấy ứng dụng web Pyramid, Flask và Django và bạn có server code, nó thực hiện máy chủ triển khai phía WSGI. Thậm chí bạn đã thấy đoạn mã ứng dụng WSGI không sử dụng bất kì framework nào. Vấn đề là khi bạn viết một ứng dụng Web sử dụng một trong những framework bạn làm việc ở một cấp độ cao hơn và không làm việc trực tiếp với WSGI, nhưng tôi biết bạn tò mò về giao diện của framework WSGI, khi bạn đang đọc về bài viết này. Vì vậy, hãy tạo một ứng dụng WSGI Web/ Web Framework không sử dụng Pyramid, Flask, or Django và chạy nó trên máy chủ của bạn.

def app(environ, start_response):
    """A barebones WSGI application.

    This is a starting point for your own Web framework :)
    """
    status = '200 OK'
    response_headers = [('Content-Type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world from a simple WSGI application!\n']

 

Lưu code phí trên với tên file wsgiapp.py  và chạy nó trên nền Web Server của bạn.

(lsbaws) $ python webserver2.py wsgiapp:app
WSGIServer: Serving HTTP on port 8888 ...

 

Nhập địa chỉ trên thanh địa chỉ và Enter. Kết quả trả về như sau:

Simple WSGI Application

 

Bạn đã viết nhiều WSGI Web Framework trong khi học làm thế nào để tạo ra một Web Server! Outrageous.

Hãy quay trở lại những gì server truyền cho client. Đây là HTTP trả về được Server tạo ra khi bạn gọi ứng dụng Pyramid sử dụng HTTP client:

HTTP Response Part 1

 

Phản hồi trả về đã có trong phần 1 nhưng nó cũng có một cái gì đó mới hơn. Ví dụ: 4 HTTP headers mà bạn chưa từng thấy trước đây Content-Type, Content-Length, Date, and Server.  Đó là những headers trả về từ Web Server nên có. Mục đích của headers là chứa thông tin HTTP request, HTTP response.

Tôi chưa nói gì đến từ điển môi trường, nhưng về cơ bản nó là từ điển Python phải chứa WSGI và CGI, biến nhất định phải theo quy định của WSGI. Máy chủ lấy các giá trị cho từ điển từ HTTP request sau khi phân tích request đó. Đây là nội dung của từ điển

Environ Python Dictionary

 

Một Web Framework sử dụng thông tin từ từ điển để quyết định cái hiển thị để sử dụng dựa trên định tuyến được chỉ định, phương thức trả về,... Nơi để đọc được phần thân yêu cầu và nơi viết lỗi, nếu có.

Bây giờ bạn đã tạo ra WSGI Web Server của chính mình và bạn đã làm được một ứng dụng Web với nhiều Web Framework khác nhau. Và bạn cũng đã tạo ứng dựng Web/Web Framework của bạn. Đó là một hành trình của một hành trình. Hãy tóm tắt lại những gì WSGI Web Server cần phải làm để đáp ứng yêu cầu của một ứng dụng WSGI.

  • Đầu tiên, Server được chạy lên và tải "ứng dụng" có thể gọi được cung cấp bởi Web Framework/Ứng dụng.
  • Server đọc request.
  • Server phân tích nó.
  • Server xây dựng một 'environ'  từ điển sử dụng dữ liệu request.
  • Server gọi 'application' có thể gọi với 'environ' từ điển và 'start_response' có thể gọi như tham số và lấy trở lại thân response.
  • Server dựng một HTTP response sử dụng dữ liệu trả về bằng cách gọi tới 'application' , trạng thái và response headers được đặt trong 'start_response' có thể gọi.
  • Và cuối cùng, server truyền HTTP response trở lại client.

Server Summary

 

Đó là tất cả nhưng gì bạn cần biết. Bạn có thể làm việc WSGI server phục vụ Web cơ bản được viết bằng Web framework như Django, Flask, Pyramid hoặc rất nhiều Web framework khác. Phần tốt nhất là máy chủ có thể sử dụng nhiều Web framework mà không cần bất kỳ thay đổi nào để có một server code cơ bản. Không tồi tệ tý nào.

Đây là một câu hỏi khác cho bạn, “How do you make your server handle more than one request at a time?” 

 
Danh sách bài viết:
Let’s Build A Web Server. Part 1: https://techmaster.vn/posts/34671/build-a-web-server-part-1
Let’s Build A Web Server. Part 2: https://techmaster.vn/posts/34672/build-a-web-server-part-2
Let’s Build A Web Server. Part 3: https://techmaster.vn/posts/34691/build-a-web-server-part-3

 

Nguồn: https://ruslanspivak.com/lsbaws-part2/