python http服务器杂谈

HTTP服务器:

在原始时代,我们使用Apache等静态内容服务器处理请求,如果是动态内容,就要借助CGI脚本。每次收到请求时,服务器将请求参数以环境变量的方式传入CGI脚本。

现在,每种语言都有自己的HTTP服务器,比如Gunicornpython也提供了socket接口方便自己编写服务器。但是自己编写的服务器在处理HTTP连接,处理静态请求,是远不如Nginx这种专业的静态服务器的,因此一般在外面用Nginx,暴露在公网中,既能架住更多请求,快速处理静态内容,又能在一个HTTP请求完全接收后(由python解析请求会很慢),再通过内部端口转发给Python自己的WSGI服务器。WSGI服务器通过端口接收到完整请求,然后调用pythonweb程序的app(environ,start_response())接口,接收应用返回的可迭代对象,start_response()web应用调用,声明响应头,可迭代对象则是返回内容的body

下面的代码是一个没有使用Nginx,直接使用自带的pythonHTTP WSGI服务器实现一个web页面。

# python3.5.2
from pprint import pformat  # pprint更美观..
from wsgiref.simple_server import make_server
def app(envir, start_response):
    headers = {'Content-type':'text/plain;charset=utf-8'}  # 返回纯文本类型,并且编码为utf8
    start_response('200 OK', list(headers.items()))  # 注意参数有响应码和列表 而不是集合哦
    yield 'Here is the WSGI environment:\r\n\r\n'.encode('utf-8')  # html换行是\r\n哦 并且要编码
    yield pformat(envir).encode('utf-8')  # 返回yield可迭代对象 迭代到出错为止
if __name__ == "__main__":
    httpd = make_server('', 8001, app)  # 空的参数是port 默认0.0.0.0
    host, port = httpd.socket.getsockname()
    print("Serving on", host, "port", port)
    httpd.serve_forever()

通过127.0.0.1这个回环端口访问这个页面,没想到显示出来的WSGIenv这么多,将近200条。


关于python中间件:就是将app()函数用装饰器或者类的方式包装,提供app()之外的功能。比如认证等等。

Flaskapp= Flask(__name__)中,Flask类就是一个中间件。为app提供了许多额外的功能。(以后再详解中间件。。)


但是WSGI有一点不好,它是同步调用,也就是在当在后台处理io操作时,比如数据库的读取,文件的读取时不能让出控制权给服务器,也就造成了它需要多线程来处理请求,app.run(Threaded=True)。如果你想使用异步框架,就需要使用与之配套的异步服务器,它们之间使用自定的方法回调,或者让出控制权给服务器。

下面是aiohttp框架(较为底层的一个例子),使用asyncio(

(待补充)



关于前向代理和反向代理。前向的代表例子是:处于一个公司的员工访问谷歌,那么就可以在公司出口处架设一个缓存服务器,如果谷歌提供的头部允许缓存(ExpiresConche-Control),就可以大大节省公司的带宽。但是,由于TLS的大量使用,代理服务器往往无法读取用户请求的内容,缓存也就无从谈起。

反向代理广泛应用于大型HTTP服务。反向代理在网页header提供了允许缓存时可以直接将其返回客户端,某些动态页面也可以缓存,甚至是每秒都会更新的消息页面也可以缓存,只要将Contorlmax-age设置为1s。反向代理必须进行TLS设置。


对于Nginx来说,不仅可以加速静态页面和缓存,还可以帮助过滤请求,节省应用程序的时间。


因此若是正常网站,Nginx Gunicornweb应用是方便且足够的。如果是单纯的API,没有什么静态服务的话,只用Gunicornweb应用也是可以的。


了解了需要的架构,下面要自己动手写一个符合WSGI协议的Web应用了。服务器已经帮忙过滤了不合法的请求,将符合规则的完整请求传递给web应用。

首先,不使用web框架来构建。web框架的一大作用是URL分发,将请求发送给合适的处理函数。我们需要阅读WSGI的具体说明,了解环境变量字典的具体属性。

下面是一个简单的例子:

import time
from wsgiref.simple_server import make_server
def app(envir, start_response):
    host = envir.get('HTTP_HOST', '127.0.0.1')  # 字典取值的用法
    path = envir.get('PATH_INFO', '/')
    if ':' in host:
        host, port = host.split(':', 1)  # 1是分割次数
    if '?' in path:
        path, query = path.split('?', 1)
    headers = [('Content-Type', 'text/plain; charset=utf-8')]
    if envir['REQUEST_METHOD'] != 'GET':
        start_response('501 Not Implement', headers)
        yield b'501 Not Implement'
    elif host != '127.0.0.1' or path != '/':
        start_response('404 Not Found', headers)
        yield b'404 Not Found'
    else:
        start_response('200 OK', headers)
        yield time.ctime().encode('utf-8')
if __name__ == "__main__":
    httpd = make_server('', 8000, app)  
    host, port = httpd.socket.getsockname()
    print("Serving on", host, "port", port)
    httpd.serve_forever()

可以看出,这个app的逻辑很繁琐,需要验证访客是不是127.0.0.1,需要自己动手分割并处理请求URL,分析请求方法,这些其实可以放到某些工具里,比如大名鼎鼎的Werkzeug库,把envir封装为Request对象,那么下面是两个Werkzeug的使用例子,第二个实现了和刚才程序一样的功能。


from werkzeug.wrappers import Request, Response
def app(envir, start_response):
    request = Request(envir)
    text = 'Hello,{}'.format(request.name.get('name', 'world'))
    response = Response(text, mimetype='text/plain')
    return response(envir, start_response)  # 参数为什么有envir我不太明白,先这样,以后再琢磨
from werkzeug.wrappers import Request, Response import time @Request.application def app(request):   # 甚至只需要一个参数request..     host = request.host     if ':' in host:              #  只有端口要自己分离         host, port = host.split(':', 1)     if request.method != 'GET':         return Response('501 Not Implement', status=501)     elif host != '127.0.0.1' or request.path != '/':  # path直接给出来了         return Response('404 Not Found', status=404)     else:         return Response(time.ctime())   # 200就不用标明啦 

还有一个Werkzeug官网的例子,这个广泛用到了中间件的思想,在app外包装了好几层,这样就省了很多编码。

Step1: 创建目录

在开始之前,首先为应用创建一个目录:

/shortly
    /static
    /templates

这个简洁的目录不是一个python包,他用来存放我们的项目文件。我们的入口模块将会放在/shortly目录的根目录下。/static目录用来放置CSSJavascript等静态文件,用户可以通过HTTP协议直接访问。/templates目录用来存放Jinja2模板文件,接下来你为项目创建的模板文件将要放到这个文件夹内。

Step2: 基本结构

现在我们正式开始为我们的项目创建模块。在shortly目录创建shortly.py文件。首先来导入一些东西。为了防止混淆,我把所有的入口放在这,即使他们不会立即使用:

import os
import redis
import urlparse
from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.wsgi import SharedDataMiddleware
from werkzeug.utils import redirect
from jinja2 import Environment, FileSystemLoader

接下来我们来为我们的应用创建基本的结构,并通过一个函数来创建应用实例,通过WSGI中间件输出static目录的文件:

 class Shortly(object):

    def __init__(self, config):
        self.redis = redis.Redis(config['redis_host'], config['redis_port'])

    def dispatch_request(self, request):
        return Response('Hello World!')    # 返回WSGIResponse对象

    def wsgi_app(self, environ, start_response):
        request = Request(environ)
        response = self.dispatch_request(request)
        return response(environ, start_response)  # 返回Response对象

    def __call__(self, environ, start_response):
        return self. wsgi_app(environ, start_response)


def create_app(redis_host='localhost', redis_port=6379, with_static=True):            # 函数作用:将Shortly生成的Response对象上再上一层中间件SharedDataMiddleware,负责在运行app之前检验是否有静态文件,有就直接返回。
    app = Shortly({
        'redis_host':       redis_host,
        'redis_port':       redis_port
    })
    if with_static:
        app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
            '/static':  os.path.join(os.path.dirname(__file__), 'static')
        })
    return app

最后我们添加一部分代码来开启一个本地服务器,自动加载代码并开启调试器:

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    app = create_app()
    run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)

create_app可以被用于创建一个新的应用实例。他不仅可以通过参数配置应用,还可以选择性的添加中间件来输出静态文件。通过这种方法我们甚至可以不配置服务器就能访问静态文件,这对开发是很有帮助的。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值