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. 【javascript】:Highcharts实战

    PS: Highcharts是一款前端图标设计框架,非常绚. 前端JS: var probabilityStatisticsData; var yearTool; var CoordinateX = ...

  2. 结合阿里云服务器,设置家中jetson tk1随时远程登陆

    前提条件: 1.路由配置dmz主机为tk1的ip ,设置路由器中ssh 端口22的访问权限 2.有一台远程服务器,服务器安装了php可以运行php文件(我使用的是阿里云) 家中tk1配置: 脚本pyt ...

  3. BOOTSTRAP定制

    1.补充:栅格系统中调整列的位置/顺序 (1)方法1:偏移量(col-*-offset-*) (2)方法2:对列进行push/pull操作 col-lg-pull-1        ~         ...

  4. AngularJS实现单页应用的原理——路由(Route)

    AngularJS实现单页应用的原理——路由(Route) 路由:告诉你一个通往某个特定页面的途径 http://127.0.0.1/index.html#/start http://127.0.0. ...

  5. Python小练习一

    # 对http://www.something.com形式的URL进行分割 url = input("Please enter the URL:") domain = url[11 ...

  6. 小试牛刀--利用豆瓣API爬取豆瓣电影top250

    最近得赶进度爬点东西,对于豆瓣,它为开发者提供了API,目前是v2版本,目前key不对个人开放,但是可以正常通过其提供的API获取数据.豆瓣V2版API权限分3类:公开.高级.商务,我们用开放基本数据 ...

  7. 解决 IE 6/7 中console对象兼容性问题

    话不多说,直接上代码 (function (){ //创建空console对象,避免JS报错 if(!window.console) window.console = {}; var console ...

  8. ANSI C 所有的转义字符

    \a 响铃符 \b 回退符 \f 换页符 \n 换行符 \r 回车符 \t 横向制表符 \v 纵向制表符 \\ 反斜杠 \? 问号 \' 单引号 \" 双引号 \000 八进制数 \xhh ...

  9. 62. Unique Paths && 63 Unique Paths II

    https://leetcode.com/problems/unique-paths/ 这道题,不利用动态规划基本上规模变大会运行超时,下面自己写得这段代码,直接暴力破解,只能应付小规模的情形,当23 ...

  10. [LeetCode]447 Number of Boomerangs

    Given n points in the plane that are all pairwise distinct, a "boomerang" is a tuple of po ...