原教程 https://defn.io/2018/02/25/web-app-from-scratch-01/
HTTP协议是怎么工作的呢?
(这个是不是看一下TCP/IP协议这本书会比较好?)
- HTTP客户端通过网络连接到HTTP服务器,并发送一系列的请求,服务器解释了这些请求之后向客户端做出回应。RFC2616文档有具体过程(但是我并不想看)
request格式(客户端的请求)
HTTP method(比如 get、post) + 路径 +HTTP协议版本+回车键或者换行符\n\r诸如此类 比如: GET /some-path HTTP/1.1\r\n request后面可以跟很多header,每一句header都要跟着他的名字,大概长这样:
Host: example.com\r\n
Accept: text/html\r\n
\r\n
最后结尾的地方要跟着\r\n (我们平时在写的时候 回车就是\n但是在html里回车是\r\n,所以要转义一下)
举个例子:(header name有多少种,分别是什么意思呢?)
Response格式(服务器的回应)
第一行是状态栏: http协议版本+response status code 比如:HTTP/1.1 200 OK\r\n 后面跟着header line(具体格式和request里面的一样) header line之后跟一个空的行 ,然后才是具体的回应主体 打个栗子:
实现一个简单的server,可以读取服务器上的文件
先知道socket是什么东西
具体看csapp中的网络编程 这个网址讲的也蛮清楚的:https://www.ibm.com/docs/en/i/7.3?topic=programming-how-sockets-work
典型的socket连接图:
-
服务端要先创建或者绑定一个地址(port)这样客户端就可以找到服务器想要发送数据的那个门 -
当绑定好地址之后,服务端等待客户端 -
当客户端也绑定好这个地址之后,就建立了连接,可以进行数据交换了
具体代码实现过程
预先定义部分: step1:首先要定义一些server会发送给client的response 这里要注意的是http的response回应长什么样(见上)
step2:再定义一个Request类,这个函数用于读取由client传来的request 这里可以调用一个读行的函数,将client传过来的request逐行读取
step3:再将原本的request转化成所需的request类,其中包括(method,path,headers)
step4:定义一个文件服务函数,如果client访问的路径是正确的,将传送文件的response发送给client,并且将文件传送过去(在这个例子当中是用html充当本地文件)
step5:现在可以server创建socket通信了,先绑定已经定义好的地址和端口了;对这个端口进行监听;一直到有client对他进行访问(也就是监听到了客户端的socket和客户端的地址),调用文件服务函数
import socket
import typing
import mimetypes
import os
HOST = "127.0.0.1"
PORT = 9000
SERVER_ROOT = os.path.abspath("www")
FILE_RESPONSE_TEMPLATE = """\
HTTP/1.1 200 OK
Content-type:{content_type}
Content-length:{content_length}
""".replace("\n","\r\n")
BAD_REQUEST_RESPONSE = b"""\
HTTP/1.1 400 Bad Request
Content-type: text/html
Content-length: 11
Bad Request""".replace(b"\n",b"\r\n")
NOT_FOUND_RESPONSE = b"""\
HTTP/1.1 404 Not Found
Content-type: text/plain
Content-length: 9
Not Found""".replace(b"\n", b"\r\n")
METHOD_NOT_ALLOWED_RESPONSE = b"""\
HTTP/1.1 405 Method Not Allowed
Content-type: text/plain
Content-length: 17
Method Not Allowed""".replace(b"\n", b"\r\n")
def iter_lines(sock:socket.socket,bufsize:int = 16_384) ->typing.Generator[bytes,None,bytes]:
buff = b""
while True:
data = sock.recv(bufsize)
if not data:
return b""
buff += data
while True:
try:
i = buff.index(b"\r\n")
line,buff = buff[:i],buff[i+2:]
if not line:
return buff
yield line
except IndexError:
break
class Request(typing.NamedTuple):
method:str
path:str
headers:typing.Mapping[str,str]
@classmethod
def from_socket(cls,sock:socket.socket)->"Request":
lines = iter_lines(sock)
try:
request_line = next(lines).decode("ascii")
except StopIteration:
raise ValueError("Requesting line missing.")
try:
method,path,_=request_line.split(" ")
except ValueError:
raise ValueError(f"Malformed request line {request_line!r}")
headers = {}
for line in lines:
try:
name,_,value = line.decode("ascii").partition(":")
headers[name.lower()] = value.lstrip()
except ValueError:
raise ValueError(f"Malformed header line {line!r}")
return cls(method = method.upper(), path = path, headers = headers)
def serve_file(sock:socket.socket,path:str)->None:
if path =="/":
path = "/index5.html"
abspath = os.path.normpath(os.path.join(SERVER_ROOT,path.lstrip("/")))
if not abspath.startswith(SERVER_ROOT):
sock.sendall(NOT_FOUND_RESPONSE)
return
try:
with open(abspath,"rb") as f:
stat = os.fstat(f.fileno())
content_type,encoding = mimetypes.guess_type(abspath)
if content_type is None:
content_type = "application/cotet-stream"
if encoding is not None:
content_type += f";charset = {encoding}"
response_headers = FILE_RESPONSE_TEMPLATE.format(
content_type = content_type,
content_length = stat.st_size,
).encode("ascii")
sock.sendall(response_headers)
print(response_headers)
sock.sendfile(f)
except FileExistsError:
sock.sendall(NOT_FOUND_RESPONSE)
return
with socket.socket() as server_sock:
server_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server_sock.bind((HOST,PORT))
server_sock.listen(0)
print(f"Listening on {HOST}:{PORT}...")
while True:
client_sock, client_addr = server_sock.accept()
print(f"New connection from {client_addr}.")
with client_sock:
try:
request = Request.from_socket(client_sock)
if request.method !="GET":
client_sock.sendall(METHOD_NOT_ALLOWED_RESPONSE)
continue
serve_file(client_sock,request.path)
except Exception as e:
print(f"Failed to parse request:{e}")
client_sock.sendall(BAD_REQUEST_RESPONSE)
|