用Python写了一个WebServer

用 Python 写了一个 WebServer , 有 BaseHTTPServer 可用, 但是用这个的感觉就像我用 vb.net 里面的 HttpListener , 因为我已经用这个写过一个 vb.net 的HttpServer

test: http://new.twd2.net:88

#!/usr/bin/python
import sys, socket, threading, os, urllib, mimetypes, time
import fastcgi, getopt

server_name='wds/1.0'
listen_bind=('0.0.0.0', 0)
conn_timeout=60
conn_list=[]
datablock=8192 *5
host_list={}
max_connection=1000


class HttpConnection(threading.Thread):
    def __init__(self, fd, addr):
        threading.Thread.__init__(self)
        self.server_name=server_name
        self.fd=fd
        self.addr=addr
        self.running=True
        self.alive_time=time.time()
        self.req={}
        self.serv_addr=listen_bind[0]
        self.serv_port=listen_bind[1]
        #print(addr[0])

    def sendfile(self, filename, offset=0, maxend=-1):
        filesize=os.path.getsize(filename)
        if maxend>-1:
            filesize=min(filesize, maxend)
        fread=offset
        fileh=open(filename, 'rb')
        fileh.seek(offset)
        while fread+datablock<filesize:
            self.alive_time=time.time()
            buff=fileh.read(datablock)
            self.fd.send(buff)
            fread+=datablock
        else:
            self.alive_time=time.time()
            buff=fileh.read(filesize-fread)
            self.fd.send(buff)
            
    def kill(self):
        self.running=False
        self.fd.shutdown(socket.SHUT_RDWR)
        
    def readline(self):
        r=''
        while r.find('\n')<0:
            r+=self.fd.recv(1)
        return r    
            
    def getrequest(self):
        #global method, req_path, httpver, req
        #fdfile=self.fd.makefile()
        mrh=self.readline()#fdfile.readline()
        if mrh=='':
            self.kill()
            return
        mrh=mrh.replace('\r', '')
        mrh=mrh.replace('\n', '')
        mrh=mrh.split(' ')
        #print(mrh)
        if len(mrh)<=1:
            self.kill()
            return
        self.method=mrh[0].upper()
        self.req_path=mrh[1]
        self.uri=self.req_path
        self.httpver=mrh[2]
        self.req={}
        #parse request data
        while True:
            self.alive_time=time.time()
            r=self.readline()#fdfile.readline()
            r=r.replace('\r', '')
            r=r.replace('\n', '')
            #print(r)
            if r=='':
                break
            r=r.split(':', 1)
            t=r[0]
            v=r[1].strip()
            self.req[t.lower()]=v
        if 'host' in self.req and ':' in self.req['host']:
            self.req['host']=self.req['host'][0:self.req['host'].find(':')]
            
    def abspath(self, root, path):
        os.chdir(root)
        if path[0:1]=='/':
            path=path[1:len(path)]
        r=os.path.abspath(path)
        if r.find(root)!=0:
            return root
        return r
        
    def sendresponse(self, res, code, des):
        #fdfile=self.fd.makefile()
        self.fd.send('HTTP/1.1 ' + str(code) + ' ' + des + '\r\n')
        #print(res)
        for t in res:
            #print(t)
            v=res[t]
            #print(v)
            #print(t, v)
            self.fd.send(t + ': ' + v + '\r\n')
        self.fd.send('\r\n')
        
    def sendtext(self, msg):
        fdfile=self.fd.makefile()
        fdfile.write(msg)

    def run(self):
        try:
            while self.running:
                self.res={'Server': server_name, 'Cache-Control': 'private'}
                code=200
                des='OK'
                if len(conn_list)>max_connection:
                    code=503
                    des='Service Unavailable'
                    txt=des
                    self.res['Content-Type']='text/html'
                    self.res['Content-Length']=str(len(txt))
                    self.sendresponse(self.res, code, des)
                    self.sendtext(txt)
                    break
                #recv=self.fd.recv(1024)
                self.getrequest()
                
                if self.httpver!='HTTP/1.1':
                    code=505
                    des='HTTP Version Not Supported'
                    txt=des
                    self.res['Content-Type']='text/html'
                    self.res['Content-Length']=str(len(txt))
                    self.sendresponse(self.res, code, des)
                    self.sendtext(txt)
                    self.kill()
                    break
                
                if not self.running:
                    return
                if 'host' in self.req and self.req['host'] in host_list:
                    host=host_list[self.req['host']]
                elif '*' in host_list:
                    host=host_list['*']
                else:
                    code=400
                    des='Bad Request(Invalid Hostname)'
                    txt=des
                    self.res['Content-Type']='text/html'
                    self.res['Content-Length']=str(len(txt))
                    self.sendresponse(self.res, code, des)
                    self.sendtext(txt)
                    continue
                #print(host)
                #print(req_path)
                id=self.req_path.find('?')
                self.querystring=''
                if id>=0:
                    self.querystring=self.req_path[id+1:len(self.req_path)]
                    self.req_path=self.req_path[0:id]
                    
                    
                #print(self.req_path)
                if os.path.isdir(self.abspath(host['path'], self.req_path)) and self.req_path[len(self.req_path)-1:len(self.req_path)]!='/':
                    self.res['Location']='http://' + self.req['host'] 
                    if listen_bind[1]!=80:
                        self.res['Location']+=':' + str(listen_bind[1])
                    self.res['Location']+=self.req_path + '/'
                    if self.querystring!='':
                        self.res['Location']+='?' + self.querystring
                    code=301
                    des='Moved Permanently'
                    self.res['Content-Length']='0'
                    self.sendresponse(self.res, code, des)
                    continue
                
                self.req_path=urllib.unquote(self.req_path)
                self.req_path=self.abspath(host['path'], self.req_path)
                #print(self.req_path)

                
                if os.path.isdir(self.req_path):
                    #looking for default file
                    for defile in host['default']:
                        if(self.abspath(self.req_path, defile)):
                            self.req_path=self.abspath(self.req_path, defile)
                            break
                elif not os.path.isfile(self.req_path):
                    script=host['path']
                    filelist=self.req_path[len(host['path']):len(self.req_path)].split('/')
                    for file in filelist:
                        if file=='':
                            continue
                        script+='/' + file
                        #print(script)
                        if os.path.isfile(script) and 'application/x-httpd-php' in mimetypes.guess_type(script):
                            self.req_path=script
                            break
                self.root=os.path.dirname(self.req_path)
                if self.root[len(self.root)-1:len(self.root)]!='/':
                    self.root+='/'
                self.root=self.root[len(host['path']):len(self.root)]
                #print(self.req_path, self.root)
                #print(self.req_path)
                if os.path.isfile(self.req_path) and mimetypes.guess_type(self.req_path)[0]!=None:
                    if 'application/x-httpd-php' in mimetypes.guess_type(self.req_path):
                        #if 'expect' in self.req:
                        #    exp=self.req['expect'].split('-', 1)
                        #    code=int(exp[0])
                        #    des=exp[1].capitalize()
                        #    self.sendresponse({}, code, des)
                            
                        self.script_name=self.req_path[len(host['path']):len(self.req_path)]
                        self.res['Cache-Control']='no-cache'
                        fcgi=fastcgi.fastcgi(('127.0.0.1', 9000), self)
                        fcgi.process()
                        break
                    filestat=os.stat(self.req_path)
                    last_modified=time.localtime(filestat.st_mtime)
                    self.res['Last-Modified']=time.strftime('%a, %d %b %Y %H:%M:%S GMT', last_modified)
                    if 'if-modified-since' in self.req:
                        #print(res['Last-Modified'], self.req['if-modified-since'])
                        if self.res['Last-Modified']==self.req['if-modified-since']:
                            code=304
                            des='Not modified'
                            self.res['Content-Length']='0'
                            self.res['Content-Type']=mimetypes.guess_type(self.req_path)[0]
                            self.sendresponse(self.res, code, des)
                            continue
                    if self.method!='GET':
                        postlen=int(self.req['content-length'])
                        code=405
                        des='Method Not Allowed'
                        txt=des
                        self.req_path=self.abspath(host['path'], host['500'])
                        #self.fd.send(b'''HTTP/1.1 500 Not supported method\nServer: WDServer\nContent-Type: text/html\nContent-Length: 20\n\nNot supported method''')
                        #self.fd.close()
                        #return 
                else:
                    code=404
                    des='Not found'
                    txt=des
                    self.req_path=self.abspath(host['path'], host['404'])
                    
                if os.path.isfile(self.req_path):
                    offset=0
                    maxend=-1
                    self.res['Content-Type']=mimetypes.guess_type(self.req_path)[0]
                    self.res['Content-Length']=str(os.path.getsize(self.req_path))
                    if 'range' in self.req:
                        rdata=self.req['range'].split('=', 1)
                        if rdata[0]=='bytes':
                            rng=rdata[1].split('-', 1)
                            offset=int(rng[0])
                            if rng[1]!='':
                                maxend=int(rng[1])
                        if maxend>-1:
                            self.res['Content-Length']=str(maxend-offset+1)
                            self.res['Content-Range']='bytes ' + str(offset) + '-' + str(maxend) + '/' + str(os.path.getsize(self.req_path))
                            code=206
                            des='Partial Content'
                    self.sendresponse(self.res, code, des)
                    self.sendfile(self.req_path, offset, maxend)
                else:
                    #txt='.conf error!'
                    self.res['Content-Type']='text/html'
                    self.res['Content-Length']=str(len(txt))
                    self.sendresponse(self.res, code, des)
                    self.sendtext(txt)
                if not 'connection' in self.req or self.req['connection'].lower()!='keep-alive':
                    self.kill()
                    return
            #self.fd.close()
        except:
            print('err')
        try:
            self.kill()
        except:
            return
def listen():
    global conn_list
    
    http_listener=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    http_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    http_listener.bind(listen_bind)
    http_listener.listen(1024)
    conn_lock=threading.Lock()
    while(True):
        conn_fd, addr=http_listener.accept()
        conn_lock.acquire()
        conn=HttpConnection(conn_fd, addr)
        conn.start()
        try:
            now=time.time()
            for conn in conn_list:
                if now-conn.alive_time > conn_timeout:
                    if conn.running:
                        conn.kill()
            conn_list=[conn for conn in conn_list if conn.running]
            #print(conn_list)
            #break
        except:
            print(sys.exc_info())
        conn_list.append(conn)
        conn_lock.release()
    http_listener.close()
    
def load_config():
    global listen_bind
    confile=open('WS.conf', 'r')
    lines=confile.readlines()
    bind=lines[0]
    bind=bind.replace('\r', '')
    bind=bind.replace('\n', '')
    bind=bind.split(' ')
    del lines[0]
    listen_bind=(bind[0], int(bind[1]))
    for host in lines:
        host=host.replace('\r', '')
        host=host.replace('\n', '')
        host=host.split(' ')
        thishost={'name': host[0], 'path': os.path.abspath(host[2])}
        if os.path.isfile(thishost['path'] + '/' + host[0] + '.conf'):
            subconf=open(thishost['path'] + '/' + host[0] + '.conf', 'r')
            sublines=subconf.readlines()
            for i in range(0, 3):
                sublines[i]=sublines[i].replace('\r', '')
                sublines[i]=sublines[i].replace('\n', '')
            thishost['default']=sublines[0].split(', ')
            thishost['404']=sublines[1]
            thishost['500']=sublines[2]
        else:
            thishost['default']={'index.htm', 'index.html', 'index.php'}
            thishost['404']=''
            thishost['500']='' 
        host_list[host[1]]=thishost
    #print(host_list)
    
def start():
    pid=0#os.fork()
    if pid==0:
        try:
            load_config()
            listen()
        except KeyboardInterrupt: 
            print('Exit()')
            for conn in conn_list:
                if conn.running:
                    try:
                        conn.kill()
                    except:
                        print(sys.exc_info())
            exit()
    else:
        print('PID: ' + str(pid))
        fo=open('wds.pid', 'w')
        fo.write(str(pid))
        fo.close()
        #sys.stdin.read(1)
        #os.system('kill -9 ' + str(pid))
        #print('Killed')
    
def main():
    try:
        opts, args=getopt.getopt(sys.argv[1:], 'sp', ['start', 'stop'])
        for n, v in opts:
            if n in ('-s', '--start'):
                start()
            if n in ('-p', '--stop'):
                pid='0'
                if os.path.isfile('wds.pid'):
                    fo=open('wds.pid', 'r')
                    pid=fo.readline()
                    fo.close
                if pid!='0':
                    os.system('kill -9 ' + pid)
                    print('Killed: ' + pid)
                else: 
                    print('Not running.')
                fo=open('wds.pid', 'w')
                fo.write('0')
                fo.close()
    except:
        print(sys.exc_info())


if __name__=='__main__':
    main()
发表评论?

5 条评论。

  1. 唉,我没有写代码的时间。。。
    羡慕你能有足够的时间写代码。

发表评论

注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

:wink: :twisted: :roll: :oops: :mrgreen: :lol: :idea: :evil: :cry: :arrow: :?: :-| :-x :-o :-P :-D :-? :) :( :!: 8-O 8)

本文链接:https://twd2.me/archives/1677QrCode