python实现简单http服务器

这实现http服务器之前,需要给大家补充一点知识,http协议。

首先http协议是基于tcp协议的,这里会用到我们前几天写的tcp服务器的知识。

我们暂且把http协议当做一个规定,就是说在浏览器访问一个页面时候,浏览器会发送一些东西给服务器,那么你发送的这些东西就是基于http协议发送的。

以百度为例我们来看一下在访问的时候,浏览器给服务器发送了什么?

1、打开谷歌浏览器

2、F12,打开开发者模式

3、输入www.baidu.com

会看到如图所示:

我们只需要先了解前两个:

GET / HTTP/1.1:GET表示请求,/表示访问主页,HTTP/1.1表示http协议1.1版本

Host:网址或者ip地址

看一下百度服务器给我们返回了什么

这些也是根据http协议返回的,那么必须有的是什么?

必须有的是第一条:HTTP/1.1 200 OK

其他都可以没有,但是我们模拟肯定要返回数据。

上面这张图叫做Headers,就是头,那我们的body部分就是我们的html页面,有了html页面,页面才变得好看。

浏览器如何区分是headers部分还是body部分?

很简单,中间加一个空行。了解了这些,我们来实现一个简单的http服务器。

其实http协议是基于tcp协议的。http协议在tcp的基础上,对服务器返回的数据的一些规定。

http服务器实例

import socket

def dump_data(cli_socket):

# 接收浏览器发送来的请求

recv_data = cli_socket.recv(1024)

# 输出浏览器给我们发送了什么

print(recv_data)

# 更具上面的http协议模拟返回数据,\r\n表示换行

resp_data = 'HTTP/1.1 200 OK\r\n'

# 一行空数据,区别headers部分和body部分

resp_data += '\r\n'

# body部分

resp_data += '<h1>hello,word.</h1>'

cli_socket.send(resp_data.encode('utf-8'))

cli_socket.close()

def main():

tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

tcp_server.bind(("",7891))

tcp_server.listen(128)

print('等待')

while True:

cli_socket,cli_addr = tcp_server.accept()

dump_data(cli_socket)

tcp_server.close()

if __name__ == "__main__":

main()

我们运行,会显示等待,等待我们的浏览器发送请求,我们打开浏览器,输入127.0.0.1:7891

就会看到我们发送给浏览器的内容,127.0.0.1表示本地,用冒号隔开,7891是我tcp服务器的端口号。这样我们就实现了一个简单的http服务器。

tcp的3次握手

第一次握手:客户端将标志位SYN赋值为1,随机产生一个参数赋值给seq,发送给服务器。等待服务器确定。

第二次握手:服务器通过SYN=1知道了该客户端要请求建立连接,再添加一个ACK=1,产生一个随机参数给seq,ack赋值为客户端随机数加1,发送给客户端确认连接,服务器进入SYN_RCVD状态。

第三次握手:客户端收到数据后,检查ack的值是否是随机数加1,ACK是否为1,正确就把ack加1,再发送给服务器,服务器确认数据,客户端和服务器都进入ESTABLISHED状态,完成三次握手。就可以传输数据了。

tcp的4次挥手

握手建立连接,挥手就是断开连接。

第一次挥手:简单来说就是发送一个数据表示我想断开连接,客户端进入FIN_WAIT_1状态。

第二次挥手:服务器收到数据,告诉客户端,我正在准备。请你确认是否断开。客户端进入FIN_WAIT_2状态

第三次挥手:发送一个数据表示我确认要断开。服务器进入LAST_ACK状态

第四次挥手:服务受收到消息,说我知道要关闭了,并且会发一个消息给服务器,之后进入TIME_WAIT。

根据浏览器的需求对应返回页面

这个程序相比上面的数据多了一个对url的判断而已。

这次我们用一个html文件来尝试。

我们在一个文件夹下创建一个txt文件,写上这样一段代码:

<html>

<head>

<meta charset="UTF-8">

<title>index</title>

</head>

<body>

<h1>我是登陆页面</h1>

</body>

</html>

将后缀名txt修改成html。

再创建一个create.html页面。只需要把里面的代码修改成:

<html>

<head>

<meta charset="UTF-8">

<title>create</title>

</head>

<body>

<h1>我是注册页面</h1>

</body>

</html>

接下来来写我们的python代码。

import socket

import re

def dump_data(cli_socket):

# 接收到的是bytes类型,我们解码

recv_data = cli_socket.recv(1024).decode('utf-8')

# 将我们接收到的数据以空格切开变成一个列表,便于我们取值,判断请求的那个页面

recv_data_lines = recv_data.splitlines()

print(recv_data_lines)

# 正则提取浏览器需要的页面

# 数据:GET /index.html HTTP/1.1

# 或者:POST /index.html HTTP/1.1 或者其他请求方式

f_name = re.match(r'[^/]+(/[^ ]*)',recv_data_lines[0])

resp_data = 'HTTP/1.1 200 OK\r\n'

resp_data += '\r\n'

# body部分

# 读取文件

file = open('F:'+f_name.group(1),'rb')

html_con = file.read()

file.close()

# 由于我们读取文件是以二进制的方式读取,所以不能直接相加

cli_socket.send(resp_data.encode('utf-8'))

# 分开发送就好了

cli_socket.send(html_con)

cli_socket.close()

def main():

tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

tcp_server.bind(("",7891))

tcp_server.listen(128)

print('等待')

while True:

cli_socket,cli_addr = tcp_server.accept()

dump_data(cli_socket)

tcp_server.close()

if __name__ == "__main__":

main()

运行一下,提示我们没有favicon.ico文件(图标),我们创建一个就好了(直接文本创建该名字就好了,后缀也需要修改的)

接下来运行,我们在浏览器输入127.0.0.1:7891/index.html 页面就会显示我是登陆页面,输入127.0.0.1:7891/create.html就会显示我是注册页面,当然如果找不到文件你可以试着添加一个404页面。或者返回为HTTP/1.1 404 NOT FOUND。

完整版:

import socket

import re

def dump_data(cli_socket):

recv_data = cli_socket.recv(1024).decode('utf-8')

recv_data_lines = recv_data.splitlines()

print(recv_data_lines)

ret = re.match(r'[^/]+(/[^ ]*)',recv_data_lines[0])

if ret:

f_name = ret.group(1)

if f_name=="/":

f_name = '/index.html'

try:

file = open('F:'+f_name,'rb')

except:

resp_data = 'HTTP/1.1 404 NOT FOUND\r\n'

resp_data += '\r\n'

resp_data += '404'

cli_socket.send(resp_data.encode('utf-8'))

else:

html_con = file.read()

file.close()

resp_data = 'HTTP/1.1 200 OK\r\n'

resp_data += '\r\n'

cli_socket.send(resp_data.encode('utf-8'))

cli_socket.send(html_con)

cli_socket.close()

def main():

tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

tcp_server.bind(("",7891))

tcp_server.listen(128)

print('等待')

while True:

cli_socket,cli_addr = tcp_server.accept()

dump_data(cli_socket)

tcp_server.close()

if __name__ == "__main__":

main()

本文分享自微信公众号 - python入门到放弃(python_xuexi)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-12-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏网站建设、网站制作专栏

javascript核心之DOM操作

DOM全称为Document Object Model ,即文档对象模型,是针对HTML和XML的一个API, 描绘了一个层次化的节点树,可以添加、移除和修改页...

8620
来自专栏村雨

Beautiful Soup

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

9520
来自专栏Jerry的SAP技术分享

Fiori应用全屏功能的实现 - fullscreen

Subject: Full Screen Button on Chart Container

8420
来自专栏编程技术专栏

一次完整的HTTP请求过程

a)首先会搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)

15220
来自专栏海涛技术日常

梳理srping boot 的几个核心基础知识点

spring boot之前,使用ssh或者ssm等框架开发,需要繁多的配置文件,开发效率较低,部署运维也较为麻烦,这时候为了解决这些问题spring boot ...

12820
来自专栏code秘密花园

来自offer杀手的前端面试攻略

楼主最近面试的公司不算是很多,但是我有一个几个人的小团体。个人感觉如果本篇文章的所有知识点你都掌握了,百度阿里腾讯网易京东,这五个公司,或许需要一部分运气和其他...

15530
来自专栏信息安全小学生

Spring security笔记3/4: 自定义登录页面

重命名 Case2Application.java 为 Case3Application.java

10220
来自专栏卡尼慕

Hive

由facebook开源的用于解决海量结构化日志的数据统计,后称Apache Hive 的开源项目。

14640
来自专栏前端自习课

【H5】344- 微信 H5 页面兼容性解决方案

当点击输入的时候,光标的高度和父盒子的高度一样。例如下图,左图是正常所期待的输入框光标,右边是ios的input光标。

13030
来自专栏卡尼慕

配置虚拟机

到这里,添加好iso文件,这个可以从百度上搜索下载。接着就准备开始运行虚拟机就行了。

13610

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励