Web服务器

#! /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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值