Python-simple-http-server

简介

这是一个轻量级的基于 Python http.server 编写的服务器,你可以非常容易的搭建一个 Restful API。其中一些请求的转发等参考了 SpringMVC 的设计。

支持的 Python 的版本

Python 2.7 / 3.6+ (3.5 也应该支持,没有在3.5环境测试过)

为什么要选择这个项目?

  • 轻量级
  • 支持过滤器链
  • Spring MVC 风格的请求映射配置
  • 简单易用
  • 编写风格自由

安装

pip install simple_http_server

编写控制器

配置路由信息

我们接下来,将处理请求的函数命名为 控制器函数(Controller Function)

类似 Spring MVC,我们使用描述性的方式来将配置请求的路由(在 Java 中,我们会使用标注 Annotation,而在 Python,我们使用 decorator,两者在使用时非常类似)。

基础的配置如下,该例子中,请求 /index 将会路由到当前的方法中。

from simple_http_server import request_map@request_map("/index")
def your_ctroller_function():return "<html><body>Hello, World!</body></html>"

@request_map 会接收两个参数,第二个参数会指定当前的控制器函数会处理请求中哪些方法(Method),以下的例子中的方法,仅会处理方法为 GET 的请求。

@request_map("/say_hello", method="GET")
def your_ctroller_function():return "<html><body>Hello, World!</body></html>"

method参数同时也可以是一个列表,以下的例子中,该控制器函数会处理方法为 GET、POST、PUT 的请求

@request_map("/say_hello", method=["GET", "POST", "PUT"])
def your_ctroller_function():return "<html><body>Hello, World!</body></html>"

匹配路由时,除了指定具体的路径之外,你还可以指定路径的某一段是可变的,这部方可变的部分将会作为路径参数放入请求中,如何取得这些路径参数我们将会在 取得请求参数 一节具体说明。@request_map 的配置如下。该配置下,/say/hello/to/world/say/hello/to/jhon 等 url 都能访问该控制器方法。

@request_map("/say_hello/to/{name}", method=["GET", "POST", "PUT"])
def your_ctroller_function():return "<html><body>Hello, World!</body></html>"

你可以给一个控制器函数增加多个 @request_map

@request_map("/")
@request_map("/index")
def index():return "<html><body>Hello, World!</body></html>"

取的请求中的信息

参考了 Spring MVC 的设计,获取请求方法的方式非常自由。其中最基本的方法是取得 Request 对象,该对象中包含了所有其他方式能够获取的内容。

from simple_http_server import request_map
from simple_http_server import Request@request_map("/say_hello/to/{name}", method=["GET", "POST", "PUT"])
def your_ctroller_function(req=Request()):""" 请注意关键字参数传入的默认参数是一个 Request 对象,而不是类本身。 """### 该请求的方法,可能是 "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT" 中的一个print(req.method)### 该请求的路径,就是 /say/hello/to/xxxprint(req.path)###  一个 dict 对象,包含了所有的头部参数。print(req.headers)### 一个 dict 对象,包含了请求的参数,包括了来源于QueryString与 body 中的参数# 该对象仅存储一个参数,如果同一个参数名传入了多个值,该对象只记录第一个值。print(req.parameter)### 一个 dict 对象,类似,req.parameter,但该对象包含了所有的参数# 该对象的值总是会返回一个列表,即使一个参数名只传入了一个参数。print(req.parameters)### 返回 query stringprint(req.query_string)### 返回一个 Cookies 对象,该对象是 http.cookies.SimpleCookie 的子类print(req.cookies)### 一个 dict 对象,包含了所有的路径上的参数,本例中是 {"name": "xxx"}print(req.path_values)### 请求体部分,在 3.6 中是一个 bytes 对象。2.7 中是一个 str 对象print(req.body)### 当你的请求的 Content-Type 是 application/json 时,框架会自动将请求体中的 JSON 对象加载为一个dict对象。print(req.json)return "<html><body>Hello, World!</body></html>"

我们还可以通过更直接的参数和关键字参数来获取请求中的信息,使得编码更加简洁和方便。

from simple_http_server import request_map@request_map("/say_hello/to/{name}", method=["GET", "POST", "PUT"])
def your_ctroller_function(user_name, # 传入 req.parameter["user_name"],如果该参数为空,则会响应为 400 参数错误password, # 传入 req.parameter["password"],如果参数为空,则会响应为 400 参数错误nickName="", # 传入 req.parameter["nickName"],如果参数为空,则会传入 ""age=16, # 传入 int(req.parameter["age"]),如果传入空则传入 16,如果传入的不是数字类型,会返回 400 参数错误male=True, # 传入 0, false, 空字符串 为 False,其他均为 True,如果不传入,传入这里指定的默认值skills=[], # 传入 req.parameters["skills"],会返回一个数组,如果没有传入任何的内容,则返回这里指定的数组extra={} # 传入 json.loads(req.parameter["extra"]),如果不传入则传入这里指定的 dict 对象,如果传入的字符串不是 JSON 格式,则响应为 400 参数错误):return "<html><body>Hello, World!</body></html>"

以上的是基础类型的获取,实施上,我们还提供了几个类,通过这些类,你还能快速地获取一些在请求头中,Cookies 中,请求体,路径中的信息。以下是一些代码实例:

from simple_http_server import request_map
from simple_http_server import Parameter
from simple_http_server import Parameters
from simple_http_server import Headers
from simple_http_server import Header
from simple_http_server import Cookies
from simple_http_server import Cookie
from simple_http_server import PathValue@request_map("/say_hello/to/{name}", method=["GET", "POST", "PUT"])
def your_ctroller_function(user_name=Parameter("userName", required=True), # 传入 req.parameter["userName"],如果该参数为空,则会响应为 400 参数错误password=Parameter("password", required=True), # 传入 req.parameter["password"],如果参数为空,则会响应为 400 参数错误nickName=Parameter(default=""), # 传入 req.parameter["nickName"],如果参数为空,则会传入 "",参数名和skills=Parameters(required=True), # 传入 req.parameters["skills"],会返回一个数组,如果没有传入任何的内容,则响应为 400 参数错误all_headers=Headers(), # 传入 req.headersuser_token=Header(name="userToken", required=True), # 传入 req.headers["userToken"],如果请求头中没有 "userToken" 字段,则响应为 400 参数错误all_cookies=Cookies(), # 传入 req.cookies,返回所有当前请求的 cookiesuser_info=Cookie("userInfo", required=False), # 传入 req.cookies["userInfo"],如果没有该 cookie,则响应为 400 参数错误name=PathValue("name"), # 传入 req.path_values["name"],返回路径中你路由配置中匹配 {name} 的字符串):return "<html><body>Hello, World!</body></html>"

从上述的例子我们看出,这些类中的参数均有默认值,即使不传入,也能返回正确的数据。除了上述的这些例子之外,我们还有一些额外的情况。例如请求的 Content-Type 是 application/json,然后我们将 JSON 字符串直接写入请求体中,我们可以这样获取信息:

from simple_http_server import request_map
from simple_http_server import JSONBody@request_map("/from_json_bldy", method=["post", "put", "delete"])
def your_json_body_controller_function(data=JSONBody()):###  JSONBody 是 dict 的子类,你可以直接其是一个 dict 来使用print(data["some_key"])return "<html><body>Hello, World!</body></html>"

我们也支持使用 multipart/form-data 上传文件,你可以这样获取文件:

from simple_http_server import request_map
from simple_http_server import MultipartFile@request_map("/upload", method="POST")
def upload(img=MultipartFile("img", required=True) # 如果没有传入 img 参数,或者该参数不是一个文件,均响应为 400 参数错误):root = os.path.dirname(os.path.abspath(__file__))### 获取上传文件的 content-typeprint(img.content_type)### MultipartFile.content 在 3.6 中为 bytes 类型,在 2.7 中为字符串print(img.content)### 还可以通过内置的 save_to_file 将内容直接写入到某个文件中img.save_to_file(root + "/uploads/imgs/" + img.filename)return "<!DOCTYPE html><html><body>upload ok!</body></html>"

响应请求

从上述的例子中可以看出,取得请求中的参数我们有许多方式,这个给了开发者很高的自由度来编写这些信息。而响应的方法一样具有各种方法。

上述的例子中是其中一种,我们直接返回了一个HTML格式的字符串,该框架会自动的将这个字符串响应为 text/html 格式。

除了 HTML5 格式的字符串,我们还可以返回以下的内容:

    ### 如果你返回一个 dict,那么框架将会将其响应为 application/jsonreturn {"success": True, "message": "Success!"}
    ### 你可以返回一个 XML 格式的字符串,框架会将其响应为 text/xmlreturn "<?xml><root></root>"
    ### 返回其他的字符串,框架会将其响应为 text/plainreturn "some other string value"
    ### 响应为 HTTP 错误,可以在任何适合抛出一个 HttpError 异常from simple_http_server import HttpErrorraise HttpError(404, "page not found")
    ### 如果抛出其他的异常,默认会响应为 500 服务器错误raise Exception()
    ### 返回 Redirect 对象from simple_http_server import Redirectreturn Redirect("/redirect/to/this/path")
    ### 你可以返回一个 StaticFile 类,返回一个文件,这个可以编写下载用的控制器方法from simple_http_server import StaticFilereturn StaticFile("/path/to/file.xml", content_type="text/xml")
    ### 还可以返回一个 Response 对象,这个对象可以设置更多的信息from simple_http_server import Responsefrom http.cookies import SimpleCookieres = Response(status_code=200)res.headers["Content-Type"] = "text/html"res.set_header("userToken", "user_id_token_xxxx") # set_header() 会覆盖之前设置的信息res.add_header("userToken", "xxxx") # add_header() 会增加多一个信息res.cookies = SimpleCookie()res.cookie["userInfo"] = "ABC"res.cookie["userInfo"]["path"] = "/"res.body = "<!DOCTYPE html><html><body>hello world</body></html>"return res
    ### 我们还有一个更为简便的方式,就是直接返回一个元祖(tuple)from simple_http_server import Headersfrom simple_http_server import Cookiesres_cookies = Cookies()res_cookie["userInfo"] = "ABC"res_cookie["userInfo"]["path"] = "/"### 该元祖中,这些参数的顺序其实并无关系,# 第一个返回的 int 元素会将作为请求的状态码# 但是元组中所有 Headers 对象均将会被写入到响应头中# 同样,元祖中所有 http.cookies.BaseCookies 以及其子类,含 http.cookie.SimpleCookie 以及 simple_http_server.Cookies 均会被写入响应的 cookies 中# 而元祖中第一个出现的类型在 (str, unicode, dict, StaticFile, bytes) 会被作为响应的数据。# 其他不符合条件的元素将被忽略return 200, Headers({"userToken": "user_id_token_xxx"}), res_cookie, {"success": True, "message": "success!"}, "这个字符串会被忽略"
    ### 元祖中所有的元素均不是必须的,即使是 body 也是一样,你可以省略一些没有的内容# 以下的元祖只写入了头部和响应体信息return Headers({"userToken": "user_id_token_xxx"}), {"success": True, "message": "success!"},

上述的例子中描述的通过返回信息来进行响应,你也可以通过 Response 参数来响应

from simple_http_server import request_map
from simple_http_server import Response@request_map("/say_hello")
def say_hello(res=Response()):### Response 对象就是上述我们写在返回的那个对象,所以,上面的对 headers、cookies 等的接口这个对象均有。res.body = "<html><body>Hello, world! </body></html>"res.send_response()# 如果使用 res 来发送请求,就不再在控制器函数中返回任何内容了。@request_map("/")
def redirect(res=Response()):res.send_redirect("/index")

编写过滤器

参考 Java 的设计,我们增加了过滤器链式的设计,这个给了你一定的面向切面编码的能力,虽然比较弱,但是做一些权限验证,日志记录等也是够用的。

from simple_http_server import filter_map# 请注意!过滤器的字符串配置是一个正则表达式的字符串。
@filter_map("^/tuple")
def filter_tuple(ctx):print("---------- through filter ---------------")# 在过滤器中加入一些信息到请求头中ctx.request.headers["filter-set"] = "through filter"if "user_name" not in ctx.request.parameter:ctx.response.send_redirect("/index")elif "pass" not in ctx.request.parameter:ctx.response.send_error(400, "pass should be passed")# 你也可以使用以下代码,通过抛出异常的方式来返回错误的响应信息。# raise HttpError(400, "pass should be passed")else:# 如果你需要进入下一个过滤器链条或者调用请求处理的函数,必须显式调用以下的方法。ctx.do_chain()

启动服务器

import simple_http_server.server as server
# 如果你的控制器代码(处理请求的函数)放在别的文件中,那么在你的 main.py 中,你必须将他都 import 进来。
import my_test_ctrldef main(*args):server.start()if __name__ == "__main__":main()

问题

Unicode 支持

虽然我已经尽力使得该框架在 Python 2.7 的环境下支持 unicode 字符串,但是由于 python 2.7 本身对 unicode 支持得不太友好,所以,普通的中文字可能问题不大,但是一些罕见字和字符可能还是会发生错误。而最有效的方法则是使用 Python 3。

多线程的安全性

因为这是一个“SIMPLE”的服务器,所以在很多的情况下我并没有考虑多线程的资源控制,默认的情况下,使用了 socketserver 的 ThreadingMixIn 来确保每个请求都在自己的线程当中。但是如果你在自己的代码中编写一些多线程的代码,那么例如往传入的 Headers、Request、Response 等对象中写入内容可能会导致多线程安全性问题。

Python http.server 服务器相关推荐

  1. python启动http服务_Python通过命令开启http.server服务器的方法

    前言 如果你急需一个简单的Web Server,但你又不想去下载并安装那些复杂的HTTP服务程序,比如:Apache,ISS等.那么, Python 可能帮助你.使用Python可以完成一个简单的内建 ...

  2. python udp 大文件_Python UDP服务器发送文本文件的行(Python UDP Server send lines of a text file)...

    Python UDP服务器发送文本文件的行(Python UDP Server send lines of a text file) 我需要模拟一个UDP服务器,它在无限循环中逐行发送文本文件的内容. ...

  3. 用python搭建一个服务器

    用python搭建一个服务器 新建一个python文件(要跟服务器的update文件同级),用来开启服务,命名为:server.py 写入python代码: import SimpleHTTPServ ...

  4. python搭建HTTP服务器

    文章目录 前言 一.HTTP服务器搭建 二.功能强化 前言 项目经常需要HTTP对接,模拟HTTP client请求可以使用postman测试,模拟HTTP server回复该如何处理?本文介绍通过p ...

  5. python --搭建FTP服务器

    一.了解FTP服务器 二.利用python搭建FTP服务器 1.安装 pyftpdlib 模块 2.找到pyftpdlib模块源文件所在目录 3.到 pyftpdlib目录下 4. 编写并运行FTP代 ...

  6. Python:gunicorn用于UNIX的Python WSGI HTTP服务器

    Gunicorn "Green Unicorn"是用于UNIX的Python WSGI HTTP服务器.从Ruby的Unicorn项目移植而来的fork-worker模型.Guni ...

  7. python和服务器共享文件夹,一行代码python实现文件共享服务器

    一行代码实现文件共享 在一个局域网内,需要共享一个文件夹里内容. 我们可以在任意一台有python环境的电脑上,迅速架起一个http协议的服务,然后将文件夹里的文件内容共享出来.是的仅仅需要一行代码 ...

  8. sql2005配置文件服务器,SQL server服务器版的安装方法

    SQL server服务器版是我们最常用的SQL server版本之一,下面就教您如何在Windows xp系统上安装SQL server服务器版的方法. 一.找一张SQL server服务器版光盘, ...

  9. C#列出局域网中可用SQL Server服务器(续)

    上一篇文章展示了使用COM对象如何列出局域网中的 SQL Server服务器信息,后来还发现在.Net中有现成的类可用,而不需要使用不太熟悉的COM对象了,这样岂不是更好?下面我把代码展示给大家: u ...

最新文章

  1. 统计学习方法第四章朴素贝叶斯法-李航
  2. Hibernate:组合模式解决树的映射
  3. 微信小程序自定义变量使用,静态变量
  4. Web API 接口
  5. #js#简单的在线计算器
  6. 【guava】GuavaCache缓存失效的时候做一些操作 RemovalListener
  7. mysql------事务
  8. PS打开PSD文档服务器未响应,ps打不开psd文件的解决方法
  9. shell学习资料:shell十三问
  10. Roslyn 静态分析
  11. SU2 CFD代码阅读
  12. 电脑连接宽带,给手机开热点
  13. MAC库乐队、APP残留清理
  14. 小米5s plus 刷机 国际版
  15. 十分详细的阳光十六法则
  16. AcWing 838.堆排序
  17. 双摄像头测距的OpenCV实现
  18. [体感游戏]关于体感游戏的一些思考(一)--- 开篇和“随身”物件
  19. g linux 未定义的引用_linux – 链接到静态库后的未定义引用
  20. 老调重弹-ffmpeg解码视频图像

热门文章

  1. python语言入门全集-Python语言入门(一)
  2. 专防诈骗 法国新型信用卡密码随时变
  3. 【图灵教育读书】分享读书心得,奖励精品图书!
  4. ArcGIS教程——ArcGIS快速入门
  5. 微信小程序 - 布局基础
  6. BIM技术在住宅园区物业管理中的应用及其优势
  7. 驱动篇 -- PMOS管应用
  8. 小米笔记本装linux教程视频教程,重新安装系统,小编教你小米笔记本怎么重装win10系统...
  9. php utf8生僻字,支持生僻字且自动识别utf-8编码的php汉字转拼音类_php技巧
  10. python编写的储存单位转换代码(以字节(B)为单位)