前言

不管是哪一种语言的web框架,其核心都是一致的,那就是以http协议为核心,围绕着http请求和http响应这两方面做文章。至于衍生的数据持久化(cookie session 数据库 等)只是存储手段罢了。

如果要理解好http协议,没有什么比实现一个自定义的http client客户端和http server服务器更好的方法了。

这篇博客博主就打算使用多种语言来做同一件事情:实现一个Http server,并且可以像apache那样实现静态资源的访问。


1、Http的请求过程

1.1 HTTP是一个基于TCP/IP通信协议来传递数据

这意味着可以使用建立socket的方式来监听某一端口,比如像tomcat一样监听8080端口,来实现这个web服务器。基于socket来实现client 和 server的交流。

1.2 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,通过http请求可以访问服务器上的HTML 文件, 图片文件, 查询结果等。

可以放几个简单的静态网页作为访问使用

1.3 HTTP三点注意事项:

  • HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
  • HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

socket在处理完一个请求之后应该主动断开连接,然后方便处理下一请求
服务器端向客户端发送数据时,要通过MiME-type指定数据的类型
http不保存事务的处理状态,当然具体的业务逻辑可以使用session或者cookie来保存状态

1.4 HTTP请求图示

这是相对复杂的结构,HTTPServer、CGI我们可以合在一起,为了简单起见,就不需要访问database。数据可以用cache暂存。

大概就是这种结构:

Http server兼任 web server和CGI program的作用。


2、HTTP请求消息

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

这个数据是很有格式的,我们可以通过对\r\n的切分就可以简单的在server端取出client端的数据


3、HTTP响应数据

HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

2、3 引用自菜鸟教程


使用Python实现一个Http server

第一步,实现一个监听8080端口的socket server,这个server只会连接一次就断开

# -*- coding:utf-8 -*-
import socketif __name__ == '__main__':# family: 套接字家族可以使AF_UNIX或者AF_INET# type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAMs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。s.bind(('localhost', 8080))while(True):# 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。# 该值至少为1,大部分应用程序设为5就可以了。s.listen(3)# 被动接受TCP客户端连接,(阻塞式)等待连接的到来conn, addr = s.accept()# 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。# flag提供有关消息的其他信息,通常可以忽略。request = conn.recv(1024)# 打印请求内容print request# 给客户端返回内容conn.sendall('welcome!')# 关闭连接conn.close()

运行:

测试一下是否跑起来了:

上面是一个简单的脚本,模拟了客户端

服务器成功返回了信息welcome

同时服务器也接收到了信息:

接下来继续扩展

使用浏览器访问server尝试:这是打印出来的request数据

按照

对数据进行切分,新的代码:

# -*- coding:utf-8 -*-
import socketif __name__ == '__main__':# family: 套接字家族可以使AF_UNIX或者AF_INET# type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAMs = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。s.bind(('localhost', 8080))while(True):# 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。# 该值至少为1,大部分应用程序设为5就可以了。s.listen(3)# 被动接受TCP客户端连接,(阻塞式)等待连接的到来conn, addr = s.accept()# 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。# flag提供有关消息的其他信息,通常可以忽略。request_content = conn.recv(1024)# 没有内容的连接,防止keep-alive导致错误断开if not request_content:conn.close()request_split = request_content.split('\r\n')# 请求行method, url, http_version = request_split[0].split(' ')# 请求头request_headers = {}for i in range(1, len(request_split)):if request_split[i] == '':breakelse:key, value = request_split[i].split(': ')request_headers[key] = value# 请求数据request_body = []for i in range(2+len(request_headers), len(request_split)):request_body.append(request_split[i])request_body = '\r\n'.join(request_body)# 打包成请求字典request = {'addr': addr,'method': method,'url': url,'http_version': http_version,'headers': request_headers,'body': request_body}print request# 给客户端返回内容conn.sendall('welcome!')# 关闭连接conn.close()

为了模拟客户端post数据,使用postman进行测试:

结果:

可以看到一个简单的request就被切分出来了

返回response


这个简单,按照格式写就是了:

common_response = '''
HTTP/1.1 200 OK
Content-Type: text/html<head>
<title>Hello world!</title>
</head>
<html>
<p>this is a easy python http server~</p>
<p>welcome~<p>
</html>
'''.replace('\n', '\r\n')

然后返回回去:

        # 给客户端返回内容conn.sendall(common_response)

使用浏览器访问127.0.0.1:8080

成功了!

写到现在,一个最基础的,能获取请求,能返回信息的httpServer就做好了。但是它现在的功能还是太简单,下一阶段,我们要再加个功能:

  • 静态资源的访问

在继续写之前,先把解析请求信息的部分抽离成函数,方便使用:

def parseRequest(request_content):request_split = request_content.split('\r\n')# 请求行method, url, http_version = request_split[0].split(' ')# 请求头request_headers = {}for i in range(1, len(request_split)):if request_split[i] == '':breakelse:key, value = request_split[i].split(': ')request_headers[key] = value# 请求数据request_body = []for i in range(2+len(request_headers), len(request_split)):request_body.append(request_split[i])request_body = '\r\n'.join(request_body)# 打包成请求字典request = {'addr': addr,'method': method,'url': url,'http_version': http_version,'headers': request_headers,'body': request_body}return request

建立一个static目录用来存储静态文件:

index.html:

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>test</title><link rel="stylesheet" href="all.css">
</head>
<body><img src="img.jpg" alt=""><h1 class="red">首页</h1>
</body>
</html>

all.css:

.red {color: red;
}

img.jpg

我们首先要能把这些static目录下的文件都读取:

def get_files(files_dir='.'):""" 获取某个路径所有的文件路径"""files_dir = os.path.join(os.getcwd(), files_dir)files_all = []def get_files_(files_dir='.', r_path=''):if files_dir[-1:] != '/':files_dir += '/'files = os.listdir(files_dir)for file in files:file_path = os.path.join(files_dir, file)if os.path.isdir(file_path):get_files_(file_path, file)else:if r_path:files_all.append('%s/%s' % (r_path, file))else:files_all.append(file)get_files_(files_dir)return files_alldef loadStatic(static_path='static'):statics = get_files(static_path)static_path = os.path.join(os.getcwd(), static_path)statics_dict = {}# 设置下列文件后缀使用二进制读取byte_files_suf = ('jpg', 'png')for file_name in statics:file_suf = file_name.split('.')[-1]print file_suffile_path = os.path.join(static_path, file_name)if file_suf in byte_files_suf:file = open(file_path, 'rb')else:file = open(file_path, 'r')statics_dict['/'+file_name] = file.read()file.close()return statics_dict

这边做了一个判断,将图片类型的文件使用二进制来进行了读写。
然后会将stataic目录下的所有文件读取,并按照文件相对static的路径-文件内容的键值对返回。

这样我们要做的就很简单了,根据之前获取的request.url,来匹配路由,如果匹配到了静态文件路径,就将其内容返回。

在浏览器中访问http://127.0.0.1:8080/index.html
控制台中打印出了request信息:

根据要求修改__main__

if __name__ == '__main__':# 加载静态文件statics = loadStatic()s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(('localhost', 8080))while(True):s.listen(3)conn, addr = s.accept()request_content = conn.recv(1024)# 没有内容的连接,防止keep-alive导致错误断开try:request = parseRequest(request_content)except e:conn.close()breakmime_type = {'jpg': 'image/jpeg','png': 'image/png','html': 'text/html'}file_suf = request['url'].split('.')[-1]if file_suf in mime_type:content_type = mime_type[file_suf]else:content_type = 'text/html'response = 'HTTP/1.1 200 OK\r\nContent-Type:%s\r\n\r\n' % content_typeprint request['url']if request['url'] in statics:print 'match static'response += statics[request['url']]# 给客户端返回内容conn.sendall(response)# 关闭连接conn.close()

再次访问http://127.0.0.1:8080/index.html
浏览器显示:

查看f12

到这里一个静态服务器就大功告成啦,哈哈哈哈

我们可以随便往static目录下放文件,然后重启server, 并访问对应的路径,比如,再新建个/img/img_1.jpg:

这张图片长这样:

重新运行easy_http_server_1.py,浏览器访问http://127.0.0.1:8080/img/img_1.jpg

访问成功。


总结

洋洋洒洒,一篇博客接近一万字了。

事实证明,一个简单的http server的实现并不复杂(当然是不追求性能和安全的前提下)。

代码仅仅100行左右。

下一篇博主将会进一步实现一个动态服务器。敬请期待。

博客代码github 链接:https://github.com/numb-men/easy-http-server/blob/master/easy_http_server_1.py


ps:请随便转载
ps:发现错误请评论
ps:python 2.7.15

实现一个静态web服务器、http server相关推荐

  1. 用原生Node实现一个静态web服务器

    之前一直用过Apache nginx等静态web服务器. 但强大的node.js本身就能作为独立的web服务器,不依赖与Apache  nginx 下面我们看看怎么用Node去写一个静态服务器吧 首先 ...

  2. Nodejs中搭建一个静态Web服务器,通过读取文件获取响应类型

    场景 Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以向浏览器等Web客户端提供文档,也可以放置网站文件让全世界浏览,还可以放置数据文件,让全世界下载.目前最主流的Web服务 ...

  3. go语言用html桌面,Go语言实现简单的一个静态WEB服务器

    学习Go语言的一些感受,不一定准确. 假如发生战争,JAVA一般都是充当航母战斗群的角色. 一旦出动,就是护卫舰.巡洋舰.航母舰载机.预警机.电子战飞机.潜艇等等 浩浩荡荡,杀将过去. (JVM,数十 ...

  4. 使用Node.js手撸一个建静态Web服务器,内部CV指南

    文章里有全部代码,也可以积分下载 操作步骤如上图 文章结束 话说这个键盘真漂亮~~ 文章目录 使用Node.js手撸一个建静态Web服务器 一.动静态服务器的概念 1.1 静态Web服务器概念 1.2 ...

  5. 【HTTP协议其实很简单】03.自己写一个微型静态Web服务器

    自己做一个微型静态Web服务器 这一篇简单粗暴一点,先上干货,看代码注释 JDK版本:1.8 实现自定义错误页.404页. package com.hawkon;import java.io.*; i ...

  6. 静态Web服务器-多任务版

    1. 静态Web服务器的问题 目前的Web服务器,不能支持多用户同时访问,只能一个一个的处理客户端的请求,那么如何开发多任务版的web服务器同时处理 多个客户端的请求? 可以使用多线程,比进程更加节省 ...

  7. 静态Web服务器-返回固定页面数据

    1. 开发自己的静态Web服务器 实现步骤: 编写一个TCP服务端程序 获取浏览器发送的http请求报文数据 读取固定页面数据,把页面数据组装成HTTP响应报文数据发送给浏览器. HTTP响应报文数据 ...

  8. node 创建静态web服务器(下)(处理异步获取数据的两种方式)

    接上一章. 上一章我们说创建的静态web服务器只能识别html,css,js文件,功能较为单一,且图片格式为text/html,这是不合理的. 本章,我们将解决该问题. 这里,我们先准备好一个json ...

  9. nodejs 创建一个静态资源服务器 +路由

    0.补充 1.Node.js 创建的第一个应用 1.引入 http 模块 var http = require("http"); 2.创建服务器 接下来我们使用 http.crea ...

最新文章

  1. 链路负载均衡: 高性能和高安全的同时实现
  2. java继承对象转换_java中类与对象的继承重写,存储以及自动转换和强制转换。...
  3. 史陶比尔与机器人之父
  4. 怎样保护计算机连接线,一根网线把电脑烧了:雷雨天如何保护家电?
  5. scanf 用法大全
  6. Android应用开发——文件目录
  7. [c++]代理对象模式
  8. woe分析_WOE和IV
  9. OSError: [Errno 9] Bad file descriptor
  10. 为什么自建深度学习机器?因为比AWS便宜10倍啊!
  11. 使用Lingo做灵敏度分析
  12. 中国区块链专利申请数破万:阿里巴巴、联通、复杂美稳居前三甲
  13. 国产化复旦微电子 FMQL45T900 FPGA开发板( 替代Xilinx ZYNQ ARM+FPGA 7045开发板)
  14. (P45)面向对象版表达式计算器:Storage类实现
  15. 依赖计算机英语作文,过度依赖电脑的英语作文
  16. 数学之美:数学究竟是如何被运用到生活中的?
  17. JMeter-01-性能测试基础知识介绍
  18. Git服务器搭建及仓库克隆
  19. 如何挑选品质好的服装
  20. Pytorch损失函数cross_entropy、binary_cross_entropy和binary_cross_entropy_with_logits的区别

热门文章

  1. excel公式:COUNTIF函数常规用法
  2. 位置好、房间美、价格还便宜?硅谷的长租房平台真有这么好吗?
  3. Avast:两年内第二起针对CCleaner的内网入侵活动
  4. 计算机网络——第一章:计算机网络概述
  5. 如何做APP界面设计
  6. 元学习之《Matching Networks for One Shot Learning》代码解读
  7. 堡垒机、跳板机JumpServer的搭建使用
  8. 网易技术:手游频繁崩溃”闪退”的原因是什么?
  9. java 汉字转换拼音
  10. word中的数学字体选择