查看原文
其他

来写一个属于自己的Web服务器

一野 脚本之家 2021-06-29

脚本之家

你与百万开发者在一起

1


前言


什么是Web,维基百科的描述如下:

万维网(英语:World Wide Web),亦作“WWW”、“Web”,是一个透过互联网访问的,由许多互相链接的超文本组成的系统。

—— 维基百科

我们的生活现在已经离不开互联网,我们日常使用的互联网服务中,其中的相当的一部分是Web系统所提供的,我们每天使用的搜索,新闻,购物等服务,其背后的实现,也是基于Web的技术栈,但是,Web系统究竟是怎么运作的呢?接下来我们就一起来实现一个最简单的Web服务器(使用python3),我们这里说的Web服务器是指HTTP服务器。


2

socket

socket是一种操作系统提供的进程间通信机制,简单来讲,socket是用来通信用的,它实现了TCP与UDP协议,Web系统是基于HTTP协议的,所以这里,我们使用socket来实现一个简单的Web服务器。


3

使用Python来实现一个最简单的HTTP服务器

1、新建一个文件夹WebServer,并在该文件下新建一个文件server.py

2、打开该文件,写入以下代码:

  1. # 导入socket包

  2. import socket


  3. # 服务器IP与端口

  4. HttpPort = 18080

  5. HttpHost = ('localhost', HttpPort)


  6. # 头部信息

  7. HttpResponseHeader = '''HTTP/1.1 200 OK

  8. Content-Type: text/html;charset=UTF-8


  9. '''


  10. # Header与Body分隔符

  11. LineSeparator = '

    '


  12. # HTTP响应

  13. HttpResponseBody = ''


  14. # 创建基于IPV4 TCP的socket

  15. ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


  16. # socket的绑定与监听

  17. ServerSocket.bind(HttpHost)

  18. ServerSocket.listen(100)


  19. print("server is running")

  20. # 处理客户端连接

  21. while True:

  22. HttpResponseBody = '很开心遇见你 :)'

  23. Client, Address = ServerSocket.accept()

  24. Request = Client.recv(1024).decode(encoding='utf-8')

  25. Client.sendall((HttpResponseHeader + HttpResponseBody).encode(encoding='utf-8'))

  26. Client.close()


3、我们在该目录下打开shell,并输入以下命令:

python3 server.py

我们可以看到以下输出:

我们打开浏览器,并在地址栏输入http://127.0.0.1:18080,可以看到以下输出:

上面的这一段代码的效果,我们已经看到了,但是,这段代码究竟做了什么呢?

TCP是传输层的协议,HTTP是应用层的协议,HTTP协议基于TCP协议,我们通过socket,建立起了客户端到服务器的TCP连接,服务器先进行监听,客户端发起一个连接,服务器收到该连接请求之后并同意建立连接,之后客户端向服务端发起HTTP请求,我们的服务端对该HTTP请求进行一定的解析之后,发送响应给客户端,然后关闭连接,这就完成了一次HTTP请求。


4

实现解析URL参数与用户提交的表单

1、我们更新server.py的代码如下:

  1. # 导入socket包

  2. import socket


  3. # 服务器IP与端口

  4. HttpPort = 18080

  5. HttpHost = ('localhost', HttpPort)


  6. # 头部信息

  7. HttpResponseHeader = '''HTTP/1.1 200 OK

  8. Content-Type: text/html;charset=UTF-8


  9. '''


  10. # Header与Body分隔符

  11. LineSeparator = '

    '


  12. # HTTP响应

  13. HttpResponseBody = ''


  14. # 创建基于IPV4 TCP的socket

  15. ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


  16. # socket的绑定与监听

  17. ServerSocket.bind(HttpHost)

  18. ServerSocket.listen(100)


  19. print("server is running at http://localhost:%d" % HttpPort)



  20. # 获取HTTP请求报头

  21. def get_headers(request):

  22. # 分隔符切割

  23. headers_array = request.split('
    ')

  24. # HTTP请求头首行 是请求方法 请求路径 HTTP协议版本

  25. headers = {}

  26. for header_item in headers_array[1:]:

  27. item_ = header_item.split(': ')

  28. headers[item_[0]] = item_[1]

  29. return headers



  30. # 获取URL参数

  31. def get_get_args(request_url):

  32. get_args_arr = request_url[request_url.find('?')+1:].split('&')

  33. # print(get_args_arr)

  34. get_args = {}

  35. try:

  36. for get_item in get_args_arr:

  37. item_ = get_item.split('=')

  38. get_args[item_[0]] = item_[1]

  39. return get_args

  40. except:

  41. return {}



  42. # 获取POST表单参数

  43. def get_post_args(request_body):

  44. post_args_arr = request_body.split('&')

  45. post_args = {}

  46. for post_item in post_args_arr:

  47. item_ = post_item.split('=')

  48. post_args[item_[0]] = item_[1]

  49. return post_args



  50. # 处理客户端连接

  51. while True:

  52. Client, Address = ServerSocket.accept()

  53. # 客户端的HTTP请求

  54. Request = Client.recv(1024).decode(encoding='utf-8')

  55. # 将请求分为报头与主体

  56. RequestText = Request.split(LineSeparator)

  57. # 报头

  58. RequestHeader = RequestText[0]

  59. # 主体

  60. RequestBody = RequestText[1]

  61. # 请求方法

  62. RequestMethod = RequestHeader.split(' ')[0]

  63. # 请求路径

  64. RequestUrl = RequestHeader.split(' ')[1]

  65. # 请求头部

  66. RequestHeaders = get_headers(RequestHeader)

  67. # 响应清空

  68. HttpResponseBody = ''


  69. # GET请求处理

  70. if RequestMethod == 'GET':

  71. HttpResponseBody += '''<html>

  72. Your method is GET and your request url is ''' + RequestUrl + '''

  73. <br>

  74. The following are you headers :<br>

  75. '''


  76. for item in RequestHeaders.items():

  77. HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')



  78. HttpResponseBody += 'There are your url args :<br>'


  79. URL_args = get_get_args(RequestUrl)


  80. for item in URL_args.items():

  81. HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')



  82. HttpResponseBody += '<br>The next session is post test:<br>'


  83. HttpResponseBody += '''

  84. <form action="/" method="post">

  85. <p>Text1: <input type="text" name="Text1" /></p>

  86. <p>Text2: <input type="text" name="Text2" /></p>

  87. <input type="submit" value="Submit" />

  88. </form>

  89. '''

  90. HttpResponseBody += '</html>'


  91. # POST方法的处理

  92. elif RequestMethod == 'POST':


  93. HttpResponseBody += '''<html>

  94. Your method is POST and your request url is ''' + RequestUrl + '''

  95. <br>

  96. The following are you headers :<br>

  97. '''


  98. for item in RequestHeaders.items():

  99. HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')


  100. HttpResponseBody += 'This is your form :<br>'


  101. PostArgs = get_post_args(RequestBody)


  102. for item in PostArgs.items():

  103. HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')


  104. HttpResponseBody += '''

  105. <br>Try to get test <br>

  106. <a href="http://''' + RequestHeaders['Host'] + '''/">get test</a>

  107. </html>

  108. '''


  109. # 暂未支持的请求方法

  110. else:

  111. HttpResponseBody += '''

  112. <html> So sorry

  113. ''' + RequestMethod + '''

  114. method is not support :( <br>

  115. </html>

  116. '''


  117. Client.sendall((HttpResponseHeader + HttpResponseBody).encode(encoding='utf-8'))

  118. Client.close()


2、然后执行: python3 server.py

3、我们可以看到能够如下显示:

GET请求:

POST请求:

这里我们主要是对参数进行了解析,然后并对其进行了显示,现实中,我们使用搜索引擎的时候,关键字一般都是通过GET方法来加上参数,需要用户提交用户名密码或者其他表单时,我们一般使用POST方法,有关更多的HTTP资料,请参考这里MDN 。


5

实现显示图片

1、我们在WebServer目录下新建一个名为index.html文件,并写入以下内容:

  1. <html>

  2. <body>

  3. <a href="/pages/">pages</a>

  4. </body>

  5. </html>


2、然后在WebServer目录下新建一个文件夹pages,并在该目录下新建一个名为index.html的文件,并写入以下内容:

  1. <html>

  2. I like this pic:<br>

  3. <img src="pic.jpg"/>

  4. </html>


3、在pages目录下保存一个jpg格式的图像文件,并将其命名为pic.jpg

4、这一步,我们更新server.py文件为以下内容:

  1. # 导入socket包

  2. import socket


  3. # 服务器IP与端口

  4. HttpPort = 18080

  5. HttpHost = ('localhost', HttpPort)


  6. # 头部信息

  7. HttpHtmlResponseHeader = '''HTTP/1.1 200 OK

  8. Content-Type: text/html;charset=UTF-8


  9. '''


  10. HttpImageResponseHeader = '''HTTP/1.1 200 OK

  11. Content-Type: image/jpg


  12. '''


  13. # Header与Body分隔符

  14. LineSeparator = '\r\n\r\n'


  15. # HTTP响应

  16. HttpResponseBody = ''


  17. # HTTP响应Bytes

  18. HttpResponse = ''.encode(encoding='utf-8')


  19. # 创建基于IPV4 TCP的socket

  20. ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)


  21. # socket的绑定与监听

  22. ServerSocket.bind(HttpHost)

  23. ServerSocket.listen(100)


  24. print("server is running at http://localhost:%d" % HttpPort)



  25. # 获取HTTP请求报头

  26. def get_headers(request):

  27. # 分隔符切割

  28. headers_array = request.split('\r\n')

  29. # HTTP请求头首行 是请求方法 请求路径 HTTP协议版本

  30. headers = {}

  31. for header_item in headers_array[1:]:

  32. item_ = header_item.split(': ')

  33. headers[item_[0]] = item_[1]

  34. return headers



  35. # 获取URL参数

  36. def get_get_args(request_url):

  37. get_args_arr = request_url[request_url.find('?') + 1:].split('&')

  38. # print(get_args_arr)

  39. get_args = {}

  40. try:

  41. for get_item in get_args_arr:

  42. item_ = get_item.split('=')

  43. get_args[item_[0]] = item_[1]

  44. return get_args

  45. except:

  46. return {}



  47. # 获取POST表单参数

  48. def get_post_args(request_body):

  49. post_args_arr = request_body.split('&')

  50. post_args = {}

  51. for post_item in post_args_arr:

  52. item_ = post_item.split('=')

  53. post_args[item_[0]] = item_[1]

  54. return post_args



  55. # 处理客户端连接

  56. while True:

  57. Client, Address = ServerSocket.accept()

  58. # 客户端的HTTP请求

  59. Request = Client.recv(1024).decode(encoding='utf-8')

  60. # 将请求分为报头与主体

  61. RequestText = Request.split(LineSeparator)

  62. # 报头

  63. RequestHeader = RequestText[0]

  64. # 主体

  65. RequestBody = RequestText[1]

  66. # 请求方法

  67. RequestMethod = RequestHeader.split(' ')[0]

  68. # 请求路径

  69. RequestUrl = RequestHeader.split(' ')[1].split('?')[0]

  70. # 请求头部

  71. RequestHeaders = get_headers(RequestHeader)

  72. # 响应清空

  73. HttpResponseBody = ''

  74. HttpResponse = ''.encode(encoding='utf-8')


  75. # GET方法的处理

  76. if RequestMethod == 'GET':

  77. # 请求的根目录

  78. if RequestUrl[-1] == '/':

  79. RequestUrl += 'index.html'

  80. print(RequestUrl)


  81. # 网页

  82. if RequestUrl.split('.')[-1] == 'html':

  83. try:

  84. # 打开并读取html文件

  85. res = open('.' + RequestUrl, 'rb')

  86. StaticHtml = res.read()

  87. HttpResponse += HttpHtmlResponseHeader.encode(encoding='utf-8')

  88. HttpResponse += (HttpResponseBody.encode(encoding='utf-8') + StaticHtml)

  89. res.close()

  90. except Exception:

  91. HttpResponse += HttpHtmlResponseHeader.encode(encoding='utf-8')

  92. HttpResponse += '<html><br>ERROR !<br></html>'.encode(encoding='utf-8')

  93. # 图片

  94. elif RequestUrl.split('.')[-1] == 'jpg':

  95. try:

  96. res = open('.' + RequestUrl, 'rb')

  97. ImageFile = res.read()

  98. HttpResponse += HttpImageResponseHeader.encode(encoding='utf-8')

  99. HttpResponse += (HttpResponseBody.encode(encoding='utf-8') + ImageFile)

  100. res.close()

  101. except Exception:

  102. HttpResponse += HttpImageResponseHeader.encode(encoding='utf-8')

  103. HttpResponse += ''.encode(encoding='utf-8')


  104. HttpResponse += '<br><br>The next is post test <br>'.encode(encoding='utf-8')


  105. HttpResponse += '''

  106. <form action="/" method="post">

  107. <p>Text1: <input type="text" name="Text1" /></p>

  108. <p>Text2: <input type="text" name="Text2" /></p>

  109. <input type="submit" value="Submit" />

  110. </form>

  111. '''.encode(encoding='utf-8')


  112. # POST方法的处理

  113. elif RequestMethod == 'POST':


  114. HttpResponseBody += '''<html>

  115. Your method is POST and your request url is ''' + RequestUrl + '''

  116. <br>

  117. The following are you headers :<br>

  118. '''


  119. for item in RequestHeaders.items():

  120. HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')


  121. HttpResponseBody += 'This is your form :<br>'


  122. PostArgs = get_post_args(RequestBody)


  123. for item in PostArgs.items():

  124. HttpResponseBody += ('key :' + item[0] + ",value :" + item[1] + '<br>')


  125. HttpResponseBody += '''

  126. <br>Try to get test <br>

  127. <a href="http://''' + RequestHeaders['Host'] + '''/">get test</a>

  128. </html>

  129. '''

  130. HttpResponse += (HttpHtmlResponseHeader + HttpResponseBody).encode(encoding='utf-8')


  131. # 暂未支持的请求方法

  132. else:

  133. HttpResponseBody += '''

  134. <html> So sorry

  135. ''' + RequestMethod + '''

  136. method is not support :( <br>

  137. </html>

  138. '''

  139. HttpResponse += (HttpHtmlResponseHeader + HttpResponseBody).encode(encoding='utf-8')


  140. Client.sendall(HttpResponse)

  141. Client.close()


5、我们执行 python3 server.py,并打开浏览器输入之前的地址,可以看到以下输出:

我们点击pages,可以看到我们的图片也加载出来啦!

不错,我们的服务器工作良好!


6

总结

我们从0实现了一个简单的Web服务器(HTTP服务器),从最开始的只能显示一段文本到解析用户提交的参数与表单,并实现了图片的显示与静态网页文件的加载,有没有感到成就感满满呢?不过我们的服务器还是相当的简陋,期待大家按照自己的想法去对HTTP的其他方法进行实现与扩展:)

自知才疏学浅,如果文章中有错误还请各位读者斧正,如果您觉得本文内容对您有所帮助,可以考虑请我吃包辣条~

写的不错?赞赏一下

长按扫码赞赏我

作者:一野

声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。

-END-

●  C# + HtmlAgilityPack + Dapper走一波爬虫

●  脚本之家粉丝福利,请查看!

● Envoy 500倍增长!5月Web服务器报告出炉

● 谈判失败:Oracle杀死Java EE

● 这些IT经典好书让你受用一生

● 入行AI,程序员为什么要学习NLP?

●  我爸的电脑中了勒索病毒

小贴士

返回 上一级 搜索“Java 女程序员 大数据 留言送书 运维 算法 Chrome 黑客 Python JavaScript 人工智能 女朋友 MySQL 书籍 等关键词获取相关文章推荐。

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存