python 实现web框架simfish

本文主要记录本人利用python实现web框架simfish的过程。源码github地址:simfish

WSGI HTTP Server

wsgi模块提供了简单的simple_server,

wsgiref.simple_server.make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler)

官方提供的例子,

from wsgiref.simple_server import make_server, demo_app

httpd = make_server('', 8000, demo_app)
print "Serving HTTP on port 8000..." # Respond to requests until process is killed
httpd.serve_forever() # Alternative: serve one request, then exit
httpd.handle_request()

访问http://127.0.0.1:8000来检查是否正常运行。

因此,有了wsgi的帮助,我们只需要实现我们自己的demo_app了。

demo_app

demo_app接受两个参数environ和start_response,其中environ包含包含所有cgi和wsgi的参数变量,start_response是一个函数,参数为status和headers,返回结果为列表或__iter__的可迭代实例。

def demo_app(environ,start_response):
from StringIO import StringIO
stdout = StringIO()
print >>stdout, "Hello world!"
print >>stdout
h = environ.items(); h.sort()
for k,v in h:
print >>stdout, k,'=', repr(v)
start_response("200 OK", [('Content-Type','text/plain')])
return [stdout.getvalue()]

实现自己的hello_app,替换demo_app即可。

def hello_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']

更多的基础细节请参考:http://www.tuicool.com/articles/aYBRBz

simfish的实现

web框架其实并不复杂,仅仅负责请求的分发和处理,让web后台开发变得简单、规范的一种方法。

本框架主要参考了bottle和webpy的源码。

路由

使用过web框架的人对路由并不陌生,路由就是用来记录url和callback function的映射关系。

在simfish中实现了三种添加路由的方法(装饰器、一次加载、随时添加),源码如下(具体内容参考注释):

# Route
class Routes:
"""FrameWork Routes"""
ROUTES = {} #存储所有的url到callback function的映射 @classmethod
def add(cls, url, handler):
"""add route and handler to ROUTES"""
if not url.startswith('/'):
url = '/' + url
if re.match(r'^/(\w+/)*\w*$', url): #这里需要使用re模块来获取正确的url
cls.ROUTES[url] = handler @classmethod
def match(cls, url): #用来寻找url对象的处理函数
"""match url in ROUTES"""
if not url:
return None
url = url.strip()
route = cls.ROUTES.get(url,None) #从ROUTES中查找结果
return route @classmethod
def load_urls(cls, urls): #用于类似webpy中urls加载的方式
for item in urls:
cls.add(item[0], item[1]) def route(url, **kargs): #这是一个装饰器,@route('/')
"""Decorator for request handler. Same as Routes.route(url, handler)."""
def wrapper(handler):
Routes.add(url, handler, **kargs)
return handler
return wrapper

具体使用方法参考:routing

封装request

在demo_app中的参数environ(它是一个字典啊!!!)中包含了request中需要的所有信息,那么我们需要把environ添加到request类中,

class Request(threading.local):
"""Represents a single request using thread-local namespace"""
def bind(self, environ):
"""Bind the enviroment"""
self._environ = environ

添加获取请求方方法的方法,使用@propery让method方法可以直接调用,注意保持方法的大写(GET/POST)

@property
def method(self):
"""Returns the request method (GET,POST,PUT,DELETE,...)"""
return self._environ.get('REQUEST_METHOD', 'GET').upper()

如果获取请求参数呢?在django中使用如下的方法,

request.GET.get('param', '')
request.POST.get('param', '')

那么,我们需要把get和post的参数全部添加到一个字典中,在environ中"QUERY_STRING"包含了get的所有参数,而post的参数需要通过"wsgi.input"获取。

@property
def GET(self):
"""Returns a dict with GET parameters."""
if self._GET is None:
raw_dict = parse_qs(self.query_string, keep_blank_values=1)
self._GET = {}
for key, value in raw_dict.items():
if len(value) == 1:
self._GET[key] = value[0]
else:
self._GET[key] = value
return self._GET

其中,parse_qs是解析get参数的,推荐使用urlparse.parse_qs 和 urlparse.parse_qsl,目前cgi的已经废弃但保留是为了向后兼容。

与get请求不同之处,在post请求中需要调用cgi模块的FieldStorage来解析post请求参数。

raw_data = cgi.FieldStorage(fp=self._environ['wsgi.input'], environ=self._environ)

具体参考源码:simfish.py

封装response

在这里主要实现了response-header和response-status的封装。

class Response(threading.local):
"""Represents a single response using thread-local namespace."""
def bind(self):
"""Clears old data and creates a brand new Response object"""
self.status = 200
self.header = HeaderDict() #继承dict的header类
self.header['Content-type'] = 'text/plain'

继承自threading.local可以保证每个每个线程拥有自己的request和response,并不会相互影响。

实现template

这里使用bottle默认的模板,使用方法参考:template

发送文件

文件的发送与普通的string返回并不相同。首先,需要判断文件的权限,

if not filename.startswith(root):
response.status = 401
return "Access denied."
if not os.path.exists(filename) or not os.path.isfile(filename):
response.status = 404
return "File does not exist."
if not os.access(filename, os.R_OK):
response.status = 401
return "You do not have permission to access this file."

获取文件的类型,这里需要使用mimetypes模块,

if mimetype:
response.header['Content-type'] = mimetype
elif guessmime:
guess = mimetypes.guess_type(filename)[0]
if guess:
response.header['Content-type'] = guess

最后返回文件对象

if mimetype == 'application/octet-stream' and "Content-Disposition" not in response.header:
response.header["Content-Disposition"] = "attachment;filename=%s"%name
elif 'Last-Modified' not in response.header:
ts = time.gmtime(stats.st_mtime)
ts = time.strftime("%a, %d %b %Y %H:%M:%S +0000", ts)
response.header["Content-Length"] = stats.st_size
response.header['Last-Modified'] = ts
return open(filename, 'r')

跳转

# Redirect to another url
def redirect(url, code=307):
""" Aborts execution and causes a 307 redirect """
response.status = code
response.header['Location'] = url
raise SimFishException("")

异常处理

# Exceptions
class SimFishException(Exception):
"""A base class for exception"""
pass class HTTPError(SimFishException):
"""Jump out to error handler"""
def __init__(self, status, text):
self.output = text
self.http_status = status def __str__(self):
return self.output class BreakSimFish(SimFishException):
"""Jump out of execution"""
def __init__(self, text):
self.output = text

路由分发

在上面已经有了如何添加路由,接下来就要实现路由的分发。

class Simfish:
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
request.bind(environ) #绑定request和response
response.bind() def __iter__(self):
path = request.path
handler = Routes.match(path) #Routes类中的match方法,获取处理的callback function
result = ""
if not handler:
response.status = 404
result = "not Found"
else:
try:
result = handler(request)
except SimFishException,output: #捕获异常情况
result = output
if isinstance(result, tuple) and len(result) == 2: #返回(response_string, mimetype),自定义返回类型
response.header['Content-type'] = result[1]
result = result[0]
status = '%d %s' % (response.status, HTTP_CODES[response.status]) #获取返回status
self.start(status, list(response.header.items())) #调用start_response if hasattr(result, 'read'): #用于返回文件
if 'wsgi.file_wrapper' in self.environ:
return self.environ['wsgi.file_wrapper'](result)
else:
return iter(lambda: result.read(8192), '')
return iter(lambda: result.read(8192), '')
elif isinstance(result, basestring):
return iter([result])
else:
return iter(result)

实例

#!/usr/bin/env python
# encoding:utf8 from simfish import application, route @route('/')
def hello(request):
return "hello world" app = application(port=8086)
app.run()

参考

http://www.tuicool.com/articles/aYBRBz

http://www.bottlepy.org/docs/dev/index.html

http://webpy.org/

python 实现web框架simfish的更多相关文章

  1. Python之Web框架Django

    Python之Web框架: Django 一. Django Django是一个卓越的新一代Web框架 Django的处理流程 1. 下载地址  Python 下载地址:https://www.pyt ...

  2. Python之Web框架

    Python之Web框架: 一.  Web框架的本质: 对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env pyth ...

  3. Python之Web框架们

    Python的WEB框架 Bottle Bottle是一个快速.简洁.轻量级的基于WSIG的微型Web框架,此框架只由一个 .py 文件,除了Python的标准库外,其不依赖任何其他模块. pip i ...

  4. python各种web框架对比

    0 引言        python在web开发方面有着广泛的应用.鉴于各种各样的框架,对于开发者来说如何选择将成为一个问题.为此,我特此对比较常见的几种框架从性能.使用感受以及应用情况进行一个粗略的 ...

  5. Python3.5学习十八 Python之Web框架 Django

    Python之Web框架: 本质:Socket 引用wsgiref创建web框架 根据web框架创建过程优化所得: 分目录管理 模板单独目录 执行不同函数单独存入一个方法py文件 Web框架的两种形式 ...

  6. python之web框架(3):WSGI之web应用完善

    python之web框架(3):WSGI之web应用完善 1.上篇的web框架太low,只能实现回应固定页面.现在将它进行完善.首先将wsgi和web服务器进行分离,并给予它回复静态页面的能力. we ...

  7. python之web框架(2):了解WSGI接口

    python之web框架(2):了解WSGI接口 1.什么是wsgi接口: wsgi:Web Service Gateway Interface.它不是模块,而只是一种规范,方便web服务器和各种框架 ...

  8. python之web框架(1):完成静态页面web服务器

    python的web框架(1) 1.首先写一个最简单的web服务器,只能给客户回应一个固定的hello world的页面. from socket import * from multiprocess ...

  9. Python Flask Web 框架入门

    Python Flask 目录 本文主要借鉴 letiantian 的文章 http://www.letiantian.me/learn-flask/ 一.简介 二.安装 三.初始化Flask 四.获 ...

随机推荐

  1. c++11 实现单例模式

    C++11出来后,里面新增加了好多好用的功能 下面的单例就是使用了C++11中的标准库中的mutex和unique_prt 进行内存管理的. 此单例模式不用担心内存的释放问题 #pragma once ...

  2. Ubuntu16.04下安装.NET Core

    以下为控制台输入代码序列一.sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet ...

  3. LeetCode(131)Palindrome Partitioning

    题目 Given a string s, partition s such that every substring of the partition is a palindrome. Return ...

  4. 前端开发面试知识点大纲--摘自jackyWHJ

    前端开发面试知识点大纲:HTML&CSS:    对Web标准的理解.浏览器内核差异.兼容性.hack.CSS基本功:布局.盒子模型.选择器优先级及使用.HTML5.CSS3.移动端适应 Ja ...

  5. error while performing database login with the xxx driver

    在MyEclipse的安装路径下D:\Program Files\MyEclipse 6.0\eclipse下面找到eclipse.ini文件,用记事本打开 eclipse.ini文件 -showsp ...

  6. 2016 12 21 的project 未注释版

    #include<stack>#include<iostream>#include<queue>#include<string>#include< ...

  7. 升级到iOS9之后的相关适配

    iOS9AdaptationTips(iOS9开发学习交流群:458884057) iOS9适配系列教程[中文在页面下方]转自@iOS程序犭袁 (截至2015年9月26日共有10篇,后续还将持续更新. ...

  8. bootstrap模态框modal使用remote第二次加载显示相同内容解决办法

    bootstrap模态框modal使用remote动态加载内容,第二次加载显示相同内容解决办法 bootstrap的modal中,使用remote可以动态加载页面到modal-body中,并弹窗显示 ...

  9. allegro中焊盘的设置

    用Cadence的pad designer制作pad的时候会遇到为thermal relief和anti pad设计尺寸的问题 Thermal relief:正规的中文翻译应该叫做防散热PAD.它主要 ...

  10. 关于selenium截图

    没时间深入研究源代码,凭调试解决了非浏览器级别的滚动条截图. 首先,定位到带有滚动条的元素,通过xpath. 其次,获取scrollheight和clientheight. 第三,循环截图,循环截图的 ...