python socket 实现的简单http服务器
预备知识:
关于http 协议的基础请参考这里。
关于socket 基础函数请参考这里。
关于python 网络编程基础请参考这里。
一、python socket 实现的简单http服务器
废话不多说,前面实现过使用linux c 或者python 充当客户端来获取http 响应,也利用muduo库实现过一个简易http服务器,现在来实现一个python版
的简易http服务器,代码改编自 http://www.cnblogs.com/vamei/ and http://www.liaoxuefeng.com/
httpServer.py
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
#!/usr/bin/env python
#coding=utf-8 import socket HOST = '' #Read index.html, put into HTTP response data ''' file = open('index.html', 'r') #Read reg.html, put into HTTP response data ''' file = open('reg.html', 'r') #Read picture, put into HTTP response data ''' #Configure socket #infinite loop print 'Connect by: ', addr #deal wiht GET method #deal with POST method conn.sendall(content) |
chmod +x httpServer.py, 并运行./httpServer.py
使用浏览器当做客户端访问服务器
在httpServer.py 所在目录有index.html, reg.html, T-mac.jpg
1、访问目录: http://192.168.56.188:8000/index.html
服务器输出:
Connect by: ('192.168.56.1', 6274)
Request is:
GET /index.html HTTP/1.1
Host: 192.168.56.188:8000
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/33.0.1750.146 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
回顾代码可知我们给客户端的响应是头部+index.html, index.html如下:
1
2 3 4 5 6 7 8 9 10 |
<html>
<head> <title>Jinan University</title> </head> <body> |
进而进一步访问T-mac.jpg,由于我们在实现服务器时使用短连接,即响应一次就关掉连接,所以客户端会再发起一次连接,如下:
Connect by: ('192.168.56.1', 6275)
Request is:
GET /T-mac.jpg HTTP/1.1
Host: 192.168.56.188:8000
Connection: keep-alive
Accept: image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/33.0.1750.146 Safari/537.36
Referer: http://192.168.56.188:8000/index.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
同样地,服务器响应头部+图片的二进制数据,如下图所示:
当然你也可以直接访问 http://192.168.56.188:8000/T-mac.jpg
2、访问目录:http://192.168.56.188:8000/reg.html
服务器输出:
Connect by: ('192.168.56.1', 6282)
Request is:
GET /reg.html HTTP/1.1
Host: 192.168.56.188:8000
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/33.0.1750.146 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
同样地,我们把头部+reg.html 响应过去,reg.html 是注册表单如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GBK"> <!--meta http-equiv="refresh" content="3;url=http://www.sina.com.cn" /--> <title>register page</title> </head> <body> <form action="http://192.168.56.188:8000" method="post"> <table border="1" bordercolor="#0000ff" cellpadding=10 cellspacing=0 width=600> <tr> <th colspan="2">注册表单</th> </tr> <tr> <td>用户名称:</td> <td><input type="text" name="user" /></td> </tr> <tr> <td>输入密码:</td> <td><input type="password" name="psw" /></td> </tr> <tr> <td>确认密码:</td> <td><input type="password" name="repsw" /></td> </tr> <tr> <td>选择性别:</td> <td> <input type="radio" name="sex" value="nan" />男 <input type="radio" name="sex" value="nv" />女 </td> </tr> <tr> <td>选择技术:</td> <td> <input type="checkbox" name="tech" value="java" />JAVA <input type="checkbox" name="tech" value="html" />HTML <input type="checkbox" name="tech" value="css" />CSS </td> </tr> <tr> <td>选择国家:</td> <td> <select name="country"> <option value="none">--选择国家--</option> <option value="usa">--美国--</option> <option value="en">--英国--</option> <option value="cn">--中国--</option> </select> </td> </tr> <tr> <th colspan="2"> <input type="reset" value="清除数据" /> <input type="submit" value="提交数据" /> </th> </tr> </table> </form> </body> </html> |
我们随便填一些信息上去然后点击提交数据,如下图:
此时浏览器会访问 http://192.168.56.188:8000/
服务器输出为:
Connect by: ('192.168.56.1', 6578)
Request is:
POST / HTTP/1.1
Host: 192.168.56.188:8000
Connection: keep-alive
Content-Length: 59
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.56.188:8000
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/33.0.1750.146 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://192.168.56.188:8000/reg.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
user=simba&psw=1990&repsw=1990&sex=nan&tech=java&country=cn
注意:即表单中的name=value,以&分隔。
回顾代码,我们只是将浏览器提交的数据体直接发回去,再输出register success! 浏览器输出如下图:
如果我们把 表单中的 <form action="http://192.168.56.188:8000" method="post">
method 改成get,会是怎样的呢?
此时浏览器会访问 http://192.168.56.188:8000/?user=simba&psw=1990&repsw=1990&sex=nan&tech=java&country=cn
服务器输出为:
Connect by: ('192.168.56.1', 6382)
Request is:
GET /?user=simba&psw=1990&repsw=1990&sex=nan&tech=java&country=cn HTTP/1.1
Host: 192.168.56.188:8000
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/33.0.1750.146 Safari/537.36
Referer: http://192.168.56.188:8000/reg.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
因为我们应答回去的数据跟post一致,故浏览器看到的输出也是一样的。
在这里可以总结一下post 跟 get 提交的一些区别:
get提交,提交的信息都显示在地址栏中;对于敏感数据不安全;由于地址栏存储体积有限而不能提交大容量数据;将信息封装到了请求消息的请求行
中,而post 提交将信息封装到了请求体中。
二、CGIHTTPServer:使用静态文件或者CGI来回应请求
先看看什么是CGI (Common Gateway Interface)。CGI是服务器和应用脚本之间的一套接口标准。它的功能是当客户端访问cgi脚本文件时让服务
器程序运行此脚本程序,将程序的输出作为response发送给客户。总体的效果,是允许服务器动态的生成回复内容,而不必局限于静态文件。
支持CGI的服务器程序接收到客户的请求,根据请求中的URL,运行对应的脚本文件。服务器会将HTTP请求的信息通过环境变量的方式传递给脚本文
件,并等待脚本的输出。脚本的输出封装成合法的HTTP回复,发送给客户。CGI可以充分发挥服务器的可编程性,让服务器变得“更聪明”。服务器和
CGI 脚本之间的通信要符合CGI标准。CGI的实现方式有很多,比如说使用Apache 服务器与Perl 写的CGI脚本,或者Python 服务器与shell写的
CGI 脚本。
为了使用CGI,我们需要使用 BaseHTTPServer 包中的 HTTPServer 类来构建服务器。
# Written by Vamei
# A messy HTTP server based on TCP socket import BaseHTTPServer
import CGIHTTPServer HOST = ''
PORT = 8000 # Create the server, CGIHTTPRequestHandler is pre-defined handler
server = BaseHTTPServer.HTTPServer((HOST, PORT), CGIHTTPServer.CGIHTTPRequestHandler)
# Start the server
server.serve_forever()
CGIHTTPRequestHandler 默认当前目录下的cgi-bin和ht-bin文件夹中的文件为CGI脚本,而存放于其他地方的文件被认为是静态文件。因此需要
修改一下index.html,将其中form元素指向的action改为cgi-bin/post.py。
<head>
<title>WOW</title>
</head>
<html>
<p>Wow, Python Server</p>
<IMG src="test.jpg"/>
<form name="input" action="cgi-bin/post.py" method="post">
First name:<input type="text" name="firstname"><br>
<input type="submit" value="Submit">
</form>
</html>
创建一个cgi-bin的文件夹,并在cgi-bin中放入如下post.py文件,也就是 CGI脚本:
#!/usr/bin/env python
# Written by Vamei
import cgi
form = cgi.FieldStorage() # Output to stdout, CGIHttpServer will take this as response to the client
print "Content-Type: text/html" # HTML is following
print # blank line, end of headers
print "<p>Hello world!</p>" # Start of content
print "<p>" + repr(form['firstname']) + "</p>"
(注意:post.py 需要有可执行权限)
第一行说明了脚本所使用的语言,即Python。 cgi包用于提取请求中包含的表格信息。脚本只负责将所有的结果输出到标准输出(使用print)。
CGIHTTPRequestHandler 会收集这些输出,封装成HTTP回复,传送给客户端。
对于POST 方法的请求,它的URL需要指向一个CGI脚本(也就是在cgi-bin或者ht-bin中的文件)。CGIHTTPRequestHandler 继承自
SimpleHTTPRequestHandler,所以也可以处理GET方法和HEAD方法的请求。此时,如果URL指向CGI脚本时,服务器将脚本的运行结果传送到客户
端;当此时URL指向静态文件时,服务器将文件的内容传送到客户端。更进一步,我可以让CGI脚本执行数据库操作,比如将接收到的数据放入到数据
库中以及更丰富的程序操作,相关内容从略。
三、python 中的 WSGI
WSGI, Web Server Gateway Interface。WSGI
是Python 对CGI 进行的一种包装,核心使用Python实现,具体实现通常来说也需要使用Python,目前Django 等web框架都实现了WSGI。
WSGI 接口定义非常简单,它只要求Web 开发者实现一个函数,就可以响应HTTP请求。来看一个最简单的Web版本的“Hello, web!”:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
environ:一个包含所有HTTP请求信息的
dict
对象;start_response:一个发送HTTP响应的函数。
在application()
函数中,调用:
start_response('200 OK', [('Content-Type', 'text/html')])
就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()
函数。start_response()
函数接收两个参数,一个是HTTP响应码,一个是一组list
表示的HTTP头部,每个Header用一个包含两个str
的tuple
表示。
Python内置了一个WSGI 服务器,这个模块叫 wsgiref,它是用纯 Python 编写的WSGI 服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI 标准,但是不考虑任何运行效率,仅供开发和测试使用。
运行WSGI服务
我们先编写hello.py
,实现Web应用程序的WSGI 处理函数:
# hello.py
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')
然后,再编写一个server.py
,负责启动WSGI服务器,加载application()
函数:
# server.py
# 从wsgiref模块导入:
from wsgiref.simple_server import make_server
# 导入我们自己编写的application函数:
from hello import application
# 创建一个服务器,IP地址为空,端口是8000,处理函数是application:
httpd = make_server('', 8000, application)
print "Serving HTTP on port 8000..."
# 开始监听HTTP请求:
httpd.serve_forever()
访问 http://localhost:8000/simba 会打印 Hello, simba!
四、MVC:Model-View-Controller
(一)中我们需要自己监听端口,接收http 请求,解析 http 请求,发送http 响应(包括静态文件和访问 cgi),就好象实现了一个极简版的
apache/lighttpd/nginx;
(二/三)中利用已有的 cgi/wsgi 服务器,我们只需要实现如何响应http 请求即可。
但如上的方式需要对每一个不同的请求(参数)都实现一个函数来响应,作为真正的web 后台实现来说肯定是不适用的,我们需要将同个目录的请求
统一处理。比如一些python web 框架如 web.py 会自己实现一个 wsgi 服务器,并留出接口,让开发者更好地实现 web 应用的功能,将 url 映射
到各个不同的 python class,在 class 内会定义 GET/POST 方法,用户访问url 则由对应的 class 类来处理,如下所示。当然具体展示给用户的html 肯
定是根据得到的变量值替换而成,即模板化,不同模板有不同的语法。在Jinja2 模板中,用{{
表示一个需要替换的变量;很多时候,
name }}
还需要循环、条件判断等指令语句,在Jinja2中用{%
表示指令。
... %}
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
urls = (
"/index", ViewIndex, "/help", ViewHelp, '/authority/manager', ViewAuthorityView, '/authority/edit', ViewAuthorityEdit, '/authority/delete', ViewAuthorityDelete, '/rule/view', ViewRuleView, '/rule/result', ViewRuleResult, '/rule/edit', ViewRuleEdit, '/rule/edit_advanced', ViewRuleEditAdvanced, '/rule/manager', ViewRuleManager, '/rule/delete', ViewRuleDelete, '/rule/operator', ViewRuleOperator, '/module/show', ViewModuleShow, '/.*', ViewIndex, ) |
这就是传说中的MVC:Model-View-Controller,中文名“模型-视图-控制器”。
Python 处理URL的函数就是C:Controller,Controller 负责业务逻辑,比如检查用户名是否存在,取出用户信息等等;
包含变量{{ name }}
的模板就是V:View,View 负责显示逻辑,通过简单地替换一些变量,View最终输出的就是用户看到的HTML。
MVC 中的Model在哪?Model是用来传给View 的,这样View 在替换变量的时候,就可以从Model 中取出相应的数据。
上面的例子中,Model 就是一个dict
:
{ 'name': 'Michael' }
只是因为Python支持关键字参数,很多Web框架允许传入关键字参数,然后,在框架内部组装出一个dict
作为Model。
在实际应用中往往也会把数据库表的操作认为是Model,通过从请求获取的信息,进而在库表查询出需要替换url 的变量值。
注意:一般一个表一个Model,而且把表操作函数中的表名写死,这样如果表有字段变动,只需改动此Model,而无需修改其他调用此表操作
的地方。此外,在一个Web App 中,有多个用户会同时访问,假设以多线程模式来处理每个用户的请求,每个线程在访问数据库时都必须创建仅属于
自身的连接,对别的线程不可见,否则就会造成数据库操作混乱,此时可能需要用到 threading.local 对象。
python socket 实现的简单http服务器的更多相关文章
- Socket网络编程--简单Web服务器(6)
本来是想实现ssl连接的,但是弄了好久都不成功,就索性不做了,等以后有能力再做了.所以这一小节就是本次的最后一节了.就简单的说几个注意点. 1.加个配置文件 使用单例模式,使用一个类,该类保存一些信息 ...
- python socket编程 实现简单p2p聊天程序
目标是写一个python的p2p聊天的项目,这里先说一下python socket的基础课程 一.Python Socket 基础课程 Socket就是套接字,作为BSD UNIX的进程通信机制,取后 ...
- Socket网络编程--简单Web服务器(1)
这一次的Socket系列准备讲Web服务器.就是编写一个简单的Web服务器,具体怎么做呢?我也不是很清楚流程,所以我找来了一个开源的小的Web服务器--tinyhttpd.这个服务器才500多行的代码 ...
- 5.1 socket编程、简单并发服务器
什么是socket? socket可以看成是用户进程与内核网络协议栈的编程接口.是一套api函数. socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机间的进程间通信. 工业上使用的为t ...
- [Python之路] 实现简单Web服务器(TCP3次握手4次挥手解释)
一.如何使用Python实现一个返回固定页面的Web Server 1.使用socket创建一个TCP Server 2.接受来自浏览器的TCP链接,并接收HTTP请求 3.返回固定响应数据给浏览器 ...
- Socket网络编程--简单Web服务器(2)
上一小节通过阅读开源的Web服务器--tinyhttpd.大概知道了一次交互的请求信息和应答信息的具体过程.接下来我就自己简单的实现一个Web服务器. 下面这个程序只是实现一个简单的框架出来.这次先实 ...
- 手写网站服务器~用Python手动实现一个简单的服务器,不借助任何框架在浏览器中输出任意内容
写在前面的一些P话: 在公司网站开发中,我们往往借助于Flask.Django等网站开发框架去提高网站开发效率.那么在面试后端开发工程师的时候,面试官可能就会问到网站开发的底层原理是什么? 我们不止仅 ...
- Socket网络编程--简单Web服务器(3)
上一小节已经实现了浏览器发送请求,然后服务器给出应答信息,然后浏览器显示出服务器发送过来的网页.一切看起来都是那么的美好.这一小节就准备实现可以根据地址栏url的不同来返回指定的网页.目前还不考虑带参 ...
- Socket网络编程--简单Web服务器(4)
上一小节已经实现了对图片的传输,接下来就是判断文件是否为js,css,png等格式.我们增加一个函数用于判断格式 int WebServer::get_filetype(char *type,char ...
随机推荐
- delphi获取文件的创建/修改时间、按时间删除指定文件下的文件
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrl ...
- Android开发中遇到的问题(五)——Eclipse导入Android项目出现"Invalid project description overlaps the location of another project"错误的解决办法
一.错误描述 使用如下的命令手动创建一个Android项目 android create project -n LinearLayoutTest -t -p E:/Android/workspace/ ...
- tomcat管理页面403 Access Denied的解决方法
安装tomcat,配置好tomcat环境变量以后,访问manager app页面,出现403 Access Denied错误,解决的方法如下: 首先在conf/tomcat-users.xml文件里面 ...
- 普通用户使用docker命令免sudo权限的问题
方法很多,只写一种 操作步骤: 使用有sudo权限的帐号登录系统. 创建docker分组,并将相应的用户添加到这个分组里面. sudo usermod -aG docker your_username ...
- maven 引入外部jar包的几种方式(转)
原文链接: maven 引入外部jar包的几种方式 方式1:dependency 本地jar包 <dependency> <groupId>com.hope.cloud< ...
- ds18b20采集温度并上报服务器
交叉编译器:arm-linux-gcc-4.5.4 Linux内核版本:Linux-3.0 主机操作系统:Centos 6.5 开发板:FL2440 温度传感器:ds18b20 注:此程序的客户端是在 ...
- Grails 第一课
package helloworld import org.junit.*; import java.util.*; class HelloController { def index() { //d ...
- swift学习之常量和变量
常量:就是在初始化时(试试定义时不赋值会不会报错)赋予一个准确的值,能够在非常多地方直接用到,用letkeyword生命 变量:这个就不用说了,就是能够在下一秒你能够随便改变的量,用varkeywor ...
- 【转载】C# Graphics类具体解释
封装一个 GDI+ 画图图面. 此类不能被继承.System.Drawing 命名空间 名称 说明 Clip 获取或设置 Region.该对象限定此 Graphics 的画图区域. ClipBoun ...
- Jmeter发送Json请求
jmeter发送的post请求,可以是json请求,和普通的post请求稍微有点区别,那么怎么用jmeter发送json请求呢? 首先要找一个json请求的例子,这个例子是携程网搜索机票, 网址为:h ...