#! /usr/bin/python3.4
#在该服务版本中支持cgi,支持python版本3.0以上
#1.0版本支持静态网页
#2.0版本相比原有1.0版本更改多线程为多进程,为cgi处理打下基础,同时python多线程并不是真正的多线程
#提醒该本不能运行在Windows系统中,因为Windows不支持os.fork函数
#更新信息
#2016/7/31 支持基础认证
#2016/8/6 测试cgi功能,能够获取get信息,post信息,以及文件上传等。
#2016/8/8 支持cookie ,并且测试了利用cookie直接登录的实现。
#2016/8/8 支持目录显示,测试javascript 和css
#2016/8/9 添加发送文件时客户端异常断开连接的处理
#2016/8/10 实现php-cgi支持
#实现过程 (1.安装php-cgi 2.配置php/cgi/php.ini文件 cgi.force_redirect=0 3.设置php解释器为/usr/bin/php-cgi)
#优化doc根目录结构
#如果要在cgi发送中文需要设置charset=utf-8,这是因为标准输出使用utf-8编码
#2016/8/11 修改程序工作路径为服务器文件下doc目录,减少应路径而产生的异常,DocumentRoot=工作路径
#2016/8/12 支持主页功能(如果主页全都不在,则使用默认显示目录信息功能)
#3.0 版本
#2016/8/13 开始实现支持https,进入PythonWeb3.0版本(为了实现这个功能必须修改cgi部分的实现,详细内容看代码)
#2016/8/14 设置cgi子进程的工作路径为cgi文件所在目录,好处:减少开发编写cgi文件时应路径而产生的异常和困惑和减少理解路径带来的烦恼,你只需知道工作路径为该文件所在目录,跟编写普通python文件相同
#2016/8/15 重定向标准出错,将异常信息发送到客户端,以便客户端能够查看到出错信息
#2016/8/15 添加Connection支持,将极大的提高效率
import os
import sys
import socket
import time
import mimetypes #MIME 类型
import urllib.parse #url 编码
import base64
import ssl
import sqlite3
#验证命令
useCommand=base64.b64encode("qin:123".encode()).decode()
bufsize=1024
#web处理类
class WebHandler():
#初始化Web处理类,主要完成接受客户请求并解析,然后做出相应响应
def __init__(self,clientSock):
#print("启动web处理")
self.clientSock=clientSock
#工作路径
self.workpath=os.path.abspath("doc")
os.chdir(self.workpath)
#文档路径
self.DocumentRoot=self.workpath
#数据库名
self.dbname=os.path.abspath("sys/sys.db")
#主页
self.Directoryindex=["index.php","index.py","index.html"]
#cgi处理的文件和对应的解释器
self.cgi={".py":"/usr/bin/python3.4",".php":"/usr/bin/php-cgi"}
#初始化数据库
self.__init_db()
#Connection 支持 极大减少connection时间
while True:
self.__chat()
#结束交互
if "HTTP_CONNECTION" not in os.environ or os.environ["HTTP_CONNECTION"]=="Closed":
break
self.__close_db()
#关闭套接字
self.clientSock.close()
def __init_db(self):
self.conn=sqlite3.connect(database=self.dbname)
try:
self.conn.execute("CREATE TABLE agentes(agent VARCHAR(100))")
except:
pass
def __close_db(self):
if self.conn:
self.conn.close()
self.conn=None
def __insert_agent_db(self,agent):
if self.conn:
self.conn.execute("INSERT INTO agentes VALUES('%s')"%(agent))
self.conn.commit()
#解析请求 添加到相应的环境变量中
def __parse(self):
#使用cgi规范进行解析(存在部分环境变量没有设置,对正常使用影响不大)这将对php,python影响不大
os.environ["GATEWAY_INTERFACE"]="CGI/1.0"
os.environ["SERVER_SOFTWARE"]="PythonWeb/3.1"
self.fileobj=self.clientSock.makefile("rb",0)
try:
request_line=self.fileobj.readline().decode().strip()
except:
return -1
#print(request_line)
try:
os.environ["REQUEST_METHOD"],os.environ["PATH_INFO"],os.environ["SERVER_PROTOCOL"]=request_line.split(" ")
except:
return -1
if os.environ["REQUEST_METHOD"]=="GET" and os.environ["PATH_INFO"].find("?")>=0:
os.environ["PATH_INFO"],os.environ["QUERY_STRING"]=os.environ["PATH_INFO"].split("?")
else:
os.environ["QUERY_STRING"]=""
#使用url解码
os.environ["PATH_INFO"]=urllib.parse.unquote(os.environ["PATH_INFO"])
os.environ["QUERY_STRING"]=urllib.parse.unquote(os.environ["QUERY_STRING"])
#判断是否为cgi文件
if os.path.splitext(os.environ["PATH_INFO"])[1] in self.cgi:
os.environ["SCRIPT_NAME"]=os.environ["PATH_INFO"]
os.environ["SCRIPT_FILENAME"]=self.DocumentRoot+os.environ["SCRIPT_NAME"]
else:
os.environ["SCRIPT_NAME"]=""
os.environ["SCRIPT_FILENAME"]=""
os.environ["PATH_TRANSLATED"]=self.DocumentRoot+os.environ["PATH_INFO"]
#读取请求报头
while True:
try:
content=self.fileobj.readline()
if len(content)==2:
break
key,value=content.decode().strip().split(": ")
if key=="Accept":
os.environ["HTTP_ACCEPT"]=value
elif key=="User-Agent":
os.environ["HTTP_USER_AGENT"]=value
self.__insert_agent_db(os.environ["HTTP_USER_AGENT"])
elif key=="Host":
os.environ["SERVER_NAME"]=value
elif key=="Content-Type":
os.environ["CONTENT_TYPE"]=value
elif key=="Content-Length":
os.environ["CONTENT_LENGTH"]=value
elif key=="Authorization":
#获取验证方法和验证信息
os.environ["AUTH_METHOD"],os.environ["AUTH_INFO"]=value.split(" ")
elif key=="Cookie":
#获取cookie内容
os.environ["HTTP_COOKIE"]=value
elif key=="Connection":
os.environ["HTTP_CONNECTION"]=value
#print(os.environ["HTTP_CONNECTION"])
except:
break
self.fileobj.close()
return 0
#与客户端进行交互
def __chat(self):
#解析请求
if self .__parse()==-1:
os.environ["HTTP_CONNECTION"]="Closed"
return
#print("开始进行响应操作。")
#输出客户请求文件
if not("AUTH_METHOD" in os.environ) or not("AUTH_INFO" in os.environ) or os.environ["AUTH_METHOD"]!="Basic" or os.environ["AUTH_INFO"]!=useCommand:
print("认证失败")
self.__baisc_auth()
else:
#print("认证成功")
#如果请求不提供ACCPET和HTTP_USER_AGENT,则禁止访问
if "HTTP_ACCEPT" not in os.environ or "HTTP_USER_AGENT" not in os.environ or "SERVER_NAME" not in os.environ:
print("禁止访问")
self.__forbid()
else:
if os.path.exists(os.environ["PATH_TRANSLATED"]) :
#文件
if os.path.isfile(os.environ["PATH_TRANSLATED"]):
print("文件名:",os.environ["PATH_TRANSLATED"])
if not os.environ["SCRIPT_NAME"]:
self.__file(os.environ["PATH_TRANSLATED"])
else:
self.__cgi(os.environ["SCRIPT_FILENAME"])
#目录
elif os.path.isdir(os.environ["PATH_TRANSLATED"]) :
if os.environ["PATH_TRANSLATED"].endswith("/"):
#显示主页
if os.environ["PATH_INFO"]=="/":
self.__index()
else:
print("目录名:",os.environ["PATH_TRANSLATED"])
self.__dir(os.environ["PATH_TRANSLATED"])
else:
self.__found("https://"+os.environ["SERVER_NAME"]+os.environ["PATH_INFO"]+"/") #https
#self.__found("http://"+os.environ["SERVER_NAME"]+os.environ["PATH_INFO"]+"/") #http
else:
self.__nofile()
else:
#404 不存在
self.__nofile()
#CGI(核心部分)
def __cgi(self,filename,arg=None):
#cgi进程
#两个管道能够让父子进程进行相互通信
father_read,son_write=os.pipe()
son_read,father_write=os.pipe()
error_read,error_write=os.pipe()
pid=os.fork()
if pid<0:
print("fork CGI 子进程失败!")
elif pid==0:
#CGI 子进程
#重定向管道
os.dup2(son_read,0)
os.dup2(son_write,1)
#重定向标准出错(2016/8/15)
os.dup2(error_write,2)
os.close(father_read)
os.close(father_write)
os.close(error_read)
#关闭套接字
self.clientSock.close()
#将cgi子进程工作路径设为文件所在路径(2016/8/14)
os.chdir(os.path.dirname(filename))
#替换进程
os.execv(self.cgi[os.path.splitext(filename)[1]],(self.cgi[os.path.splitext(filename)[1]],filename,str(arg)))
else:
#CGI 父进程
os.close(son_write)
os.close(son_read)
os.close(error_write)
#将post信息发送给CGI子进程
if "CONTENT_LENGTH" in os.environ:
length=int(os.environ["CONTENT_LENGTH"])
while length>0:
post_msg=self.clientSock.recv(min(bufsize,length))
length-=len(post_msg)
os.write(father_write,post_msg)
#开始发送响应信息到客户端
self.clientSock.send("HTTP/1.1 200 OK\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Data: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: Closed\r\n".encode())
self.clientSock.send("Content-Type: text/html;charset=utf-8\r\n".encode())
#获取CGI子进程执行的结果
while True:
content=os.read(father_read,bufsize)
if not len(content):
break
else:
self.clientSock.send(content)
#读取异常
has_error=False #判断是否存在错误
while True:
content=os.read(error_read,bufsize)
if not len(content):
break
else:
if not has_error:
has_error=True
self.clientSock.send("\r\n".encode())
self.clientSock.send(content)
os.close(father_read)
os.close(father_write)
os.close(error_read)
#等待CGI子进程的结束
spid,status=os.wait()
os.environ["HTTP_CONNECTION"]="Closed"
#显示主页
def __index(self):
for index in self.Directoryindex:
if os.path.exists(os.path.join(self.DocumentRoot,index)):
print("主页:",index)
os.environ["PATH_INFO"]="/"+index
os.environ["PATH_TRANSLATED"]=os.path.join(self.DocumentRoot,index)
if os.path.splitext(os.environ["PATH_INFO"])[1] in self.cgi:
os.environ["SCRIPT_NAME"]=os.environ["PATH_INFO"]
os.environ["SCRIPT_FILENAME"]=os.environ["PATH_TRANSLATED"]
self.__cgi(os.environ["SCRIPT_FILENAME"])
else:
self.__file(os.environ["PATH_TRANSLATED"])
return
#所有主页都没有,默认显示目录
print("目录:",os.environ["PATH_TRANSLATED"])
self.__dir(os.environ["PATH_TRANSLATED"])
#转移
def __found(self,url):
self.clientSock.send("HTTP/1.1 302 Found\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: Closed\r\n".encode())
self.clientSock.send(("Location: "+url+"\r\n").encode())
self.clientSock.send("\r\n".encode())
os.environ["HTTP_CONNECTION"]="Closed"
def __file(self,filename):
#发送状态行
self.clientSock.send("HTTP/1.1 200 OK\r\n".encode())
#发送响应报头
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: keep-alive\r\n".encode())
#如果文件没后缀名的异常处理
mimetype=mimetypes.guess_type(filename)[0]
if mimetype:
self.clientSock.send(("Content-Type: "+mimetype+"\r\n").encode())
else:
self.clientSock.send("Content-Type: application/octet-stream\r\n".encode())
self.clientSock.send(("Content-Length: "+str(os.path.getsize(filename))+"\r\n").encode())
self.clientSock.send("\r\n".encode())
#发送正文内容
fs=open(filename,"rb")
while True:
filecontent=fs.read(bufsize)
if not len(filecontent):
break
try:
self.clientSock.send(filecontent)
except (BrokenPipeError,ConnectionResetError) as e:
print(e)
break
fs.close()
def __nofile(self):
#允许自定义NOTFOUND.html文件
filename=self.DocumentRoot+"/sys/NotFound.html"
self.clientSock.send("HTTP/1.1 404 Not Found\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: keep-alive\r\n".encode())
self.clientSock.send("Content-Type: text/html\r\n".encode())
self.clientSock.send(("Content-Length: "+str(os.path.getsize(filename))+"\r\n").encode())
self.clientSock.send("\r\n".encode())
fs=open(filename,"rb")
while True:
filecontent=fs.read(bufsize)
if not len(filecontent):
break
self.clientSock.send(filecontent)
fs.close()
#显示目录
def __dir(self,dirname):
#使用CGI
self.__cgi(self.DocumentRoot+"/sys/listdir.py",dirname)
#禁止访问
def __forbid(self):
#允许自定义Forbid.html文件
filename=self.DocumentRoot+"/sys/Forbid.html"
self.clientSock.send("HTTP/1.1 403 Forbidden\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: keep-alive\r\n".encode())
self.clientSock.send("Content-Type: text/html\r\n".encode())
self.clientSock.send(("Content-Length: "+str(os.path.getsize(filename))+"\r\n").encode())
self.clientSock.send("\r\n".encode())
fs=open(filename,"rb")
while True:
filecontent=fs.read(bufsize)
if not len(filecontent):
break
self.clientSock.send(filecontent)
fs.close()
#基础认证
def __baisc_auth(self):
filename=self.DocumentRoot+"/sys/Auth.html"
self.clientSock.send("HTTP/1.1 401 Unautherized\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: keep-alive\r\n".encode())
self.clientSock.send("Content-Type: text/html\r\n".encode())
#基础认证报头
self.clientSock.send("WWW-Authenticate: Basic realm=\"private\"\r\n".encode())
self.clientSock.send(("Content-Length: "+str(os.path.getsize(filename))+"\r\n").encode())
self.clientSock.send("\r\n".encode())
fs=open(filename,"rb")
while True:
filecontent=fs.read(bufsize)
if not len(filecontent):
break
self.clientSock.send(filecontent)
fs.close()
def __del__(self):
self.clientSock.close()
#web服务器类,主要完成接受客户端连接请求,然后将客户端套接字交予WebHandler进行处理
class WebServer():
#创建服务器套接字
def __init__(self):
#将工作路径设为web服务器文件所在的目录(去除应使用绝对路径而产生的异常)
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
try:
self.serverSock=socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
#设置套接字地址可重用
self.serverSock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.ssl_configure()
print("服务器初始化成功")
except BaseException as e:
print(e)
sys.exit(-1)
#配置ssl
def ssl_configure(self):
try:
self.context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)
self.context.load_cert_chain(certfile="cert.pem",keyfile="key.pem")
print("ssl配置成功")
except:
raise BaseException("ssl配置失败")
#开启Web服务器监听
def start_listen(self,port=443,count=5):
try:
#绑定地址和端口
#self.hostname=socket.gethostname()
#print("监听主机名:",self.hostname)
#监听所有ip地址
self.serverSock.bind(("",port))
self.serverSock.listen(count)
print("成功开启监听")
except BaseException as e:
print(e)
sys.exit(-1)
print("配置结束,web服务器开启监听")
self.__run()
def __run(self):
while True:
#print("等待用于连接中...")
try:
clientSock,addr=self.serverSock.accept()
except KeyboardInterrupt:
print("关闭服务")
sys.exit(0)
#print("客户",addr)
#创建子进程
pid=os.fork()
if pid<0:
print("创建子进程失败。")
elif pid>0:
#父进程
clientSock.close()
else:
#子进程
#ssl套接字
clientSock=self.context.wrap_socket(clientSock,server_side=True)
WebHandler(clientSock)
#结束子进程
sys.exit(0)
#作为模块时将不会被执行
if __name__=="__main__":
#测试
server=WebServer()
#开始监听
server.start_listen()
#在该服务版本中支持cgi,支持python版本3.0以上
#1.0版本支持静态网页
#2.0版本相比原有1.0版本更改多线程为多进程,为cgi处理打下基础,同时python多线程并不是真正的多线程
#提醒该本不能运行在Windows系统中,因为Windows不支持os.fork函数
#更新信息
#2016/7/31 支持基础认证
#2016/8/6 测试cgi功能,能够获取get信息,post信息,以及文件上传等。
#2016/8/8 支持cookie ,并且测试了利用cookie直接登录的实现。
#2016/8/8 支持目录显示,测试javascript 和css
#2016/8/9 添加发送文件时客户端异常断开连接的处理
#2016/8/10 实现php-cgi支持
#实现过程 (1.安装php-cgi 2.配置php/cgi/php.ini文件 cgi.force_redirect=0 3.设置php解释器为/usr/bin/php-cgi)
#优化doc根目录结构
#如果要在cgi发送中文需要设置charset=utf-8,这是因为标准输出使用utf-8编码
#2016/8/11 修改程序工作路径为服务器文件下doc目录,减少应路径而产生的异常,DocumentRoot=工作路径
#2016/8/12 支持主页功能(如果主页全都不在,则使用默认显示目录信息功能)
#3.0 版本
#2016/8/13 开始实现支持https,进入PythonWeb3.0版本(为了实现这个功能必须修改cgi部分的实现,详细内容看代码)
#2016/8/14 设置cgi子进程的工作路径为cgi文件所在目录,好处:减少开发编写cgi文件时应路径而产生的异常和困惑和减少理解路径带来的烦恼,你只需知道工作路径为该文件所在目录,跟编写普通python文件相同
#2016/8/15 重定向标准出错,将异常信息发送到客户端,以便客户端能够查看到出错信息
#2016/8/15 添加Connection支持,将极大的提高效率
import os
import sys
import socket
import time
import mimetypes #MIME 类型
import urllib.parse #url 编码
import base64
import ssl
import sqlite3
#验证命令
useCommand=base64.b64encode("qin:123".encode()).decode()
bufsize=1024
#web处理类
class WebHandler():
#初始化Web处理类,主要完成接受客户请求并解析,然后做出相应响应
def __init__(self,clientSock):
#print("启动web处理")
self.clientSock=clientSock
#工作路径
self.workpath=os.path.abspath("doc")
os.chdir(self.workpath)
#文档路径
self.DocumentRoot=self.workpath
#数据库名
self.dbname=os.path.abspath("sys/sys.db")
#主页
self.Directoryindex=["index.php","index.py","index.html"]
#cgi处理的文件和对应的解释器
self.cgi={".py":"/usr/bin/python3.4",".php":"/usr/bin/php-cgi"}
#初始化数据库
self.__init_db()
#Connection 支持 极大减少connection时间
while True:
self.__chat()
#结束交互
if "HTTP_CONNECTION" not in os.environ or os.environ["HTTP_CONNECTION"]=="Closed":
break
self.__close_db()
#关闭套接字
self.clientSock.close()
def __init_db(self):
self.conn=sqlite3.connect(database=self.dbname)
try:
self.conn.execute("CREATE TABLE agentes(agent VARCHAR(100))")
except:
pass
def __close_db(self):
if self.conn:
self.conn.close()
self.conn=None
def __insert_agent_db(self,agent):
if self.conn:
self.conn.execute("INSERT INTO agentes VALUES('%s')"%(agent))
self.conn.commit()
#解析请求 添加到相应的环境变量中
def __parse(self):
#使用cgi规范进行解析(存在部分环境变量没有设置,对正常使用影响不大)这将对php,python影响不大
os.environ["GATEWAY_INTERFACE"]="CGI/1.0"
os.environ["SERVER_SOFTWARE"]="PythonWeb/3.1"
self.fileobj=self.clientSock.makefile("rb",0)
try:
request_line=self.fileobj.readline().decode().strip()
except:
return -1
#print(request_line)
try:
os.environ["REQUEST_METHOD"],os.environ["PATH_INFO"],os.environ["SERVER_PROTOCOL"]=request_line.split(" ")
except:
return -1
if os.environ["REQUEST_METHOD"]=="GET" and os.environ["PATH_INFO"].find("?")>=0:
os.environ["PATH_INFO"],os.environ["QUERY_STRING"]=os.environ["PATH_INFO"].split("?")
else:
os.environ["QUERY_STRING"]=""
#使用url解码
os.environ["PATH_INFO"]=urllib.parse.unquote(os.environ["PATH_INFO"])
os.environ["QUERY_STRING"]=urllib.parse.unquote(os.environ["QUERY_STRING"])
#判断是否为cgi文件
if os.path.splitext(os.environ["PATH_INFO"])[1] in self.cgi:
os.environ["SCRIPT_NAME"]=os.environ["PATH_INFO"]
os.environ["SCRIPT_FILENAME"]=self.DocumentRoot+os.environ["SCRIPT_NAME"]
else:
os.environ["SCRIPT_NAME"]=""
os.environ["SCRIPT_FILENAME"]=""
os.environ["PATH_TRANSLATED"]=self.DocumentRoot+os.environ["PATH_INFO"]
#读取请求报头
while True:
try:
content=self.fileobj.readline()
if len(content)==2:
break
key,value=content.decode().strip().split(": ")
if key=="Accept":
os.environ["HTTP_ACCEPT"]=value
elif key=="User-Agent":
os.environ["HTTP_USER_AGENT"]=value
self.__insert_agent_db(os.environ["HTTP_USER_AGENT"])
elif key=="Host":
os.environ["SERVER_NAME"]=value
elif key=="Content-Type":
os.environ["CONTENT_TYPE"]=value
elif key=="Content-Length":
os.environ["CONTENT_LENGTH"]=value
elif key=="Authorization":
#获取验证方法和验证信息
os.environ["AUTH_METHOD"],os.environ["AUTH_INFO"]=value.split(" ")
elif key=="Cookie":
#获取cookie内容
os.environ["HTTP_COOKIE"]=value
elif key=="Connection":
os.environ["HTTP_CONNECTION"]=value
#print(os.environ["HTTP_CONNECTION"])
except:
break
self.fileobj.close()
return 0
#与客户端进行交互
def __chat(self):
#解析请求
if self .__parse()==-1:
os.environ["HTTP_CONNECTION"]="Closed"
return
#print("开始进行响应操作。")
#输出客户请求文件
if not("AUTH_METHOD" in os.environ) or not("AUTH_INFO" in os.environ) or os.environ["AUTH_METHOD"]!="Basic" or os.environ["AUTH_INFO"]!=useCommand:
print("认证失败")
self.__baisc_auth()
else:
#print("认证成功")
#如果请求不提供ACCPET和HTTP_USER_AGENT,则禁止访问
if "HTTP_ACCEPT" not in os.environ or "HTTP_USER_AGENT" not in os.environ or "SERVER_NAME" not in os.environ:
print("禁止访问")
self.__forbid()
else:
if os.path.exists(os.environ["PATH_TRANSLATED"]) :
#文件
if os.path.isfile(os.environ["PATH_TRANSLATED"]):
print("文件名:",os.environ["PATH_TRANSLATED"])
if not os.environ["SCRIPT_NAME"]:
self.__file(os.environ["PATH_TRANSLATED"])
else:
self.__cgi(os.environ["SCRIPT_FILENAME"])
#目录
elif os.path.isdir(os.environ["PATH_TRANSLATED"]) :
if os.environ["PATH_TRANSLATED"].endswith("/"):
#显示主页
if os.environ["PATH_INFO"]=="/":
self.__index()
else:
print("目录名:",os.environ["PATH_TRANSLATED"])
self.__dir(os.environ["PATH_TRANSLATED"])
else:
self.__found("https://"+os.environ["SERVER_NAME"]+os.environ["PATH_INFO"]+"/") #https
#self.__found("http://"+os.environ["SERVER_NAME"]+os.environ["PATH_INFO"]+"/") #http
else:
self.__nofile()
else:
#404 不存在
self.__nofile()
#CGI(核心部分)
def __cgi(self,filename,arg=None):
#cgi进程
#两个管道能够让父子进程进行相互通信
father_read,son_write=os.pipe()
son_read,father_write=os.pipe()
error_read,error_write=os.pipe()
pid=os.fork()
if pid<0:
print("fork CGI 子进程失败!")
elif pid==0:
#CGI 子进程
#重定向管道
os.dup2(son_read,0)
os.dup2(son_write,1)
#重定向标准出错(2016/8/15)
os.dup2(error_write,2)
os.close(father_read)
os.close(father_write)
os.close(error_read)
#关闭套接字
self.clientSock.close()
#将cgi子进程工作路径设为文件所在路径(2016/8/14)
os.chdir(os.path.dirname(filename))
#替换进程
os.execv(self.cgi[os.path.splitext(filename)[1]],(self.cgi[os.path.splitext(filename)[1]],filename,str(arg)))
else:
#CGI 父进程
os.close(son_write)
os.close(son_read)
os.close(error_write)
#将post信息发送给CGI子进程
if "CONTENT_LENGTH" in os.environ:
length=int(os.environ["CONTENT_LENGTH"])
while length>0:
post_msg=self.clientSock.recv(min(bufsize,length))
length-=len(post_msg)
os.write(father_write,post_msg)
#开始发送响应信息到客户端
self.clientSock.send("HTTP/1.1 200 OK\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Data: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: Closed\r\n".encode())
self.clientSock.send("Content-Type: text/html;charset=utf-8\r\n".encode())
#获取CGI子进程执行的结果
while True:
content=os.read(father_read,bufsize)
if not len(content):
break
else:
self.clientSock.send(content)
#读取异常
has_error=False #判断是否存在错误
while True:
content=os.read(error_read,bufsize)
if not len(content):
break
else:
if not has_error:
has_error=True
self.clientSock.send("\r\n".encode())
self.clientSock.send(content)
os.close(father_read)
os.close(father_write)
os.close(error_read)
#等待CGI子进程的结束
spid,status=os.wait()
os.environ["HTTP_CONNECTION"]="Closed"
#显示主页
def __index(self):
for index in self.Directoryindex:
if os.path.exists(os.path.join(self.DocumentRoot,index)):
print("主页:",index)
os.environ["PATH_INFO"]="/"+index
os.environ["PATH_TRANSLATED"]=os.path.join(self.DocumentRoot,index)
if os.path.splitext(os.environ["PATH_INFO"])[1] in self.cgi:
os.environ["SCRIPT_NAME"]=os.environ["PATH_INFO"]
os.environ["SCRIPT_FILENAME"]=os.environ["PATH_TRANSLATED"]
self.__cgi(os.environ["SCRIPT_FILENAME"])
else:
self.__file(os.environ["PATH_TRANSLATED"])
return
#所有主页都没有,默认显示目录
print("目录:",os.environ["PATH_TRANSLATED"])
self.__dir(os.environ["PATH_TRANSLATED"])
#转移
def __found(self,url):
self.clientSock.send("HTTP/1.1 302 Found\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: Closed\r\n".encode())
self.clientSock.send(("Location: "+url+"\r\n").encode())
self.clientSock.send("\r\n".encode())
os.environ["HTTP_CONNECTION"]="Closed"
def __file(self,filename):
#发送状态行
self.clientSock.send("HTTP/1.1 200 OK\r\n".encode())
#发送响应报头
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: keep-alive\r\n".encode())
#如果文件没后缀名的异常处理
mimetype=mimetypes.guess_type(filename)[0]
if mimetype:
self.clientSock.send(("Content-Type: "+mimetype+"\r\n").encode())
else:
self.clientSock.send("Content-Type: application/octet-stream\r\n".encode())
self.clientSock.send(("Content-Length: "+str(os.path.getsize(filename))+"\r\n").encode())
self.clientSock.send("\r\n".encode())
#发送正文内容
fs=open(filename,"rb")
while True:
filecontent=fs.read(bufsize)
if not len(filecontent):
break
try:
self.clientSock.send(filecontent)
except (BrokenPipeError,ConnectionResetError) as e:
print(e)
break
fs.close()
def __nofile(self):
#允许自定义NOTFOUND.html文件
filename=self.DocumentRoot+"/sys/NotFound.html"
self.clientSock.send("HTTP/1.1 404 Not Found\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: keep-alive\r\n".encode())
self.clientSock.send("Content-Type: text/html\r\n".encode())
self.clientSock.send(("Content-Length: "+str(os.path.getsize(filename))+"\r\n").encode())
self.clientSock.send("\r\n".encode())
fs=open(filename,"rb")
while True:
filecontent=fs.read(bufsize)
if not len(filecontent):
break
self.clientSock.send(filecontent)
fs.close()
#显示目录
def __dir(self,dirname):
#使用CGI
self.__cgi(self.DocumentRoot+"/sys/listdir.py",dirname)
#禁止访问
def __forbid(self):
#允许自定义Forbid.html文件
filename=self.DocumentRoot+"/sys/Forbid.html"
self.clientSock.send("HTTP/1.1 403 Forbidden\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: keep-alive\r\n".encode())
self.clientSock.send("Content-Type: text/html\r\n".encode())
self.clientSock.send(("Content-Length: "+str(os.path.getsize(filename))+"\r\n").encode())
self.clientSock.send("\r\n".encode())
fs=open(filename,"rb")
while True:
filecontent=fs.read(bufsize)
if not len(filecontent):
break
self.clientSock.send(filecontent)
fs.close()
#基础认证
def __baisc_auth(self):
filename=self.DocumentRoot+"/sys/Auth.html"
self.clientSock.send("HTTP/1.1 401 Unautherized\r\n".encode())
self.clientSock.send(("Server: %s\r\n"%(os.environ["SERVER_SOFTWARE"])).encode())
self.clientSock.send(("Date: "+time.ctime(time.time())+"\r\n").encode())
self.clientSock.send("Connection: keep-alive\r\n".encode())
self.clientSock.send("Content-Type: text/html\r\n".encode())
#基础认证报头
self.clientSock.send("WWW-Authenticate: Basic realm=\"private\"\r\n".encode())
self.clientSock.send(("Content-Length: "+str(os.path.getsize(filename))+"\r\n").encode())
self.clientSock.send("\r\n".encode())
fs=open(filename,"rb")
while True:
filecontent=fs.read(bufsize)
if not len(filecontent):
break
self.clientSock.send(filecontent)
fs.close()
def __del__(self):
self.clientSock.close()
#web服务器类,主要完成接受客户端连接请求,然后将客户端套接字交予WebHandler进行处理
class WebServer():
#创建服务器套接字
def __init__(self):
#将工作路径设为web服务器文件所在的目录(去除应使用绝对路径而产生的异常)
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
try:
self.serverSock=socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
#设置套接字地址可重用
self.serverSock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.ssl_configure()
print("服务器初始化成功")
except BaseException as e:
print(e)
sys.exit(-1)
#配置ssl
def ssl_configure(self):
try:
self.context=ssl.SSLContext(ssl.PROTOCOL_TLSv1)
self.context.load_cert_chain(certfile="cert.pem",keyfile="key.pem")
print("ssl配置成功")
except:
raise BaseException("ssl配置失败")
#开启Web服务器监听
def start_listen(self,port=443,count=5):
try:
#绑定地址和端口
#self.hostname=socket.gethostname()
#print("监听主机名:",self.hostname)
#监听所有ip地址
self.serverSock.bind(("",port))
self.serverSock.listen(count)
print("成功开启监听")
except BaseException as e:
print(e)
sys.exit(-1)
print("配置结束,web服务器开启监听")
self.__run()
def __run(self):
while True:
#print("等待用于连接中...")
try:
clientSock,addr=self.serverSock.accept()
except KeyboardInterrupt:
print("关闭服务")
sys.exit(0)
#print("客户",addr)
#创建子进程
pid=os.fork()
if pid<0:
print("创建子进程失败。")
elif pid>0:
#父进程
clientSock.close()
else:
#子进程
#ssl套接字
clientSock=self.context.wrap_socket(clientSock,server_side=True)
WebHandler(clientSock)
#结束子进程
sys.exit(0)
#作为模块时将不会被执行
if __name__=="__main__":
#测试
server=WebServer()
#开始监听
server.start_listen()