基於python實現簡單web服務器

作web開發的你,真的熟悉web服務器處理機制嗎?html

 

分析請求數據

 

下面是一段原始的請求數據:python

b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8000\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.8\r\nCookie: csrftoken=gnaOXtlMlCxgjq62jUzBjv6XedPpVvxotWq2R6KpTv9dK2eHCwXlApyNEl9anzp9\r\n\r\n'git

是否是有種似曾相識的感受,沒錯,打開chrome開發者工具,隨便打開一個網頁,就能夠看到如上信息,只不過發送到服務器端就是上面的格式,已\r\n分割處理。github

  • 經過分析以上內容,能夠發現各項之間都是以\r\n分割
  • 最後以2個\r\n結束,也就是咱們在響應給客戶端時,內容和響應頭中間就是用2個\r\n分割

簡單的webserver實現

 

一、靜態資源

主要思路以下:web

  1. 開啓socket服務監聽客戶端鏈接
  2. 採用多進程方式實現併發鏈接處理
  3. 基於報文分析規則,拼接響應內容,輸出到客戶端

代碼以下:chrome

`
# coding:utf-8

import socket
import re

from multiprocessing import Process

HTML_ROOT_DIR = './static'


class WebServer(object):
'''
簡單的webserver
'''

def __init__(self):
self.sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

def start(self):
self.sock_server.listen(128)
while True:
sock_client, addr = self.sock_server.accept()
print('[%s,%s]用戶已鏈接......' % addr)
handle_client_process = Process(target=self.handle_client, args=(sock_client,))
handle_client_process.start()
sock_client.close()

def handle_client(self, sock_client):
'''處理客戶端請求'''
recv_data = sock_client.recv(1024)
#print('請求數據:', recv_data)
req_lines = recv_data.splitlines()
#for line in req_lines:
# print(line)

req_start_line = req_lines[0]
#print(req_start_line.decode('utf-8'))
file_name = re.match(r'\w+ +(/[^ ]*) ', req_start_line.decode('utf-8')).group(1)
if '/' == file_name:
file_name = "/index.html"

try:
file = open(HTML_ROOT_DIR + file_name, 'rb')
except IOError:
resp_start_line = 'HTTP/1.1 404 Not Found\r\n'
resp_headers = 'Server: My Web Server\r\n'
resp_body = 'The file is not found!'
else:
file_data = file.read()
file.close()

resp_start_line = 'HTTP/1.1 200 OK\r\n'
resp_headers = 'Server: My Web Server\r\n'
resp_body = file_data.decode('utf-8')

# 構造響應內容
resp_data = resp_start_line + resp_headers + '\r\n' + resp_body
#print('構造響應內容:', resp_data)

# response
sock_client.send(bytes(resp_data, 'utf-8'))

# 關閉客戶端鏈接
sock_client.close()

def bind(self, port):
self.sock_server.bind(('', port))


def main():
webServer = WebServer()
webServer.bind(8000)
webServer.start()


if __name__ == '__main__':
main()
`
  • 地址欄輸入:127.0.0.1:8000

二、動態資源

主要思路以下:小程序

  1. 開啓socket服務監聽客戶端鏈接
  2. 採用多進程方式實現併發鏈接處理
  3. 基於報文分析規則,若是是以.py結尾的請求,自動分發到相應的模塊作處理

web服務器,代碼以下:服務器

`
# coding:utf-8

import socket
import re
import sys

from multiprocessing import Process

HTML_ROOT_DIR = './static'
WSGI_PY = './wsgipy'

class WebServer(object):
'''
簡單的webserver
'''

def __init__(self):
self.sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

def start(self):
self.sock_server.listen(128)
while True:
sock_client, addr = self.sock_server.accept()
print('[%s,%s]用戶已鏈接......' % addr)
handle_client_process = Process(target=self.handle_client, args=(sock_client,))
handle_client_process.start()
sock_client.close()

def start_response(self, status, headers):
"""
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
star
"""
resp_headers = 'HTTP/1.1 ' + status + '\r\n'
for header in headers:
resp_headers += '%s: %s\r\n' % header

self.resp_headers = resp_headers

def handle_client(self, sock_client):
'''處理客戶端請求'''
recv_data = sock_client.recv(1024)
#print('請求數據:', recv_data)
req_lines = recv_data.splitlines()
#for line in req_lines:
# print(line)

req_start_line = req_lines[0]
#print(req_start_line.decode('utf-8'))
file_name = re.match(r'\w+ +(/[^ ]*) ', req_start_line.decode('utf-8')).group(1)
method = re.match(r'(\w+) +/[^ ]*',req_start_line.decode("utf-8")).group(1)

if file_name.endswith('.py'):
try:
m = __import__(file_name[1:-3])
except Exception:
self.resp_headers = 'HTTP/1.1 404 Not Found\r\n'
resp_body = 'not found'
else:
env = {
'PATH_INFO': file_name,
'METHOD': method
}
resp_body = m.application(env, self.start_response)
resp_data = self.resp_headers+'\r\n'+resp_body
else:
if '/' == file_name:
file_name = '/index.html'

try:
file = open(HTML_ROOT_DIR + file_name, 'rb')
except IOError:
resp_start_line = 'HTTP/1.1 404 Not Found\r\n'
resp_headers = 'Server: My Web Server\r\n'
resp_body = 'The file is not found!'
else:
file_data = file.read()
file.close()

resp_start_line = 'HTTP/1.1 200 OK\r\n'
resp_headers = 'Server: My Web Server\r\n'
resp_body = file_data.decode('utf-8')

# 構造響應內容
resp_data = resp_start_line + resp_headers + '\r\n' + resp_body
#print('構造響應內容:', resp_data)

# response
sock_client.send(bytes(resp_data, 'utf-8'))

# 關閉客戶端鏈接
sock_client.close()

def bind(self, port):
self.sock_server.bind(('', port))


def main():
sys.path.insert(1,WSGI_PY)
webServer = WebServer()
webServer.bind(8000)
webServer.start()


if __name__ == '__main__':
main()

`

helloworld.py:
`
# coding:Utf-8

def application(env,start_response):
status = '200 OK'
headers = [
('Content-Type','text/plain')
]
start_response(status,headers)
return 'hello world'

主要是技術點解析:併發

  一、根據請求 .py 名稱自動導入模塊,使用了一個小魔法:m = __import__('filename') ,會返回具體應用,而後根據應用實例調用具體處理程序。app

  二、在具體的應用中設置狀態碼、響應頭;application(env,start_response)其中env是request信息,start_response是web服務器中的函數,經過調用start_response(status,headers)把狀態碼和headers傳遞給web服務器,這樣web服務器就能夠拿到status、headers拼接response

  三、具體應用中返回響應體response_body,也就是應用中的return 'hello world',結合2中的status、headers獲得最終的response data發給客戶端

 

代碼已上傳到github:python 小程序

相關文章
相關標籤/搜索