#!/usr/bin/env python3
#-*- coding: UTF-8 -*-

import os
import sys
import socket
import select
import logging
import argparse
from datetime import datetime

name = os.path.basename(__file__)
logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s",level=logging.INFO)


def block_http_server(ip='127.0.0.1',port=8080,msg='mtls-http is working ...'):
    """工作在阻塞模式下的 http 服务器
    """
    try:
        print("{0} | prepare start block http server".format(datetime.now()))
        with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as server:
            html="""<html>
                <head>
                    <title> block http server </title>
                </head>
                <body>
                    <h1>{0}</h1>
                </body>
            </html>
            """.format(msg)
            length_html = len(html.encode('utf8'))
            head = 'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 2049 01:01:01 GMT\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: {0}\r\n\r\n'.format(length_html)
            response = (head + html).encode('utf8')
            server.bind((ip,port))
            print("{0} | server binds on {1}:{2}".format(datetime.now(),ip,port))
            server.listen(5)
            while True:
                cscok,addr = server.accept()
                print("{0} | accept a client from {1}".format(datetime.now(),addr))
                request = cscok.recv(4096)
                cscok.send(response)
                cscok.close()
                print("{0} | response sended.".format(datetime.now()))
    except KeyboardInterrupt as err:
        sys.exit()

def main_loop(poll):
    """定义主事件循环
    """
    while True:
        events = poll.poll()
        for fileno,event in events:
            yield fileno,event

def aio_http_server(ip='127.0.0.1',port=8080,msg='mtls-http is working ...'):
    """基于异步IO的http服务端
    """
    logging.info("异步 http 服务器启动监听")
    html=f"""<html>
        <head>
            <title> aio http server </title>
        </head>
        <body>
            <h1>{msg}</h1>
            <p>异步 http 服务器启动时间: {datetime.now().isoformat(' ')}</p>
        </body>
    </html>
    """
    logging.info("静态 html 模板生成完成")
    length_html = len(html.encode('utf8'))
    head = f"HTTP/1.0 200 OK\r\nDate: {datetime.now().isoformat(' ')}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: {length_html}\r\n\r\n"
    rspns = (head + html).encode('utf8')
    logging.info("TCP 字节流准备完成")
    try:
        poll = select.poll()
        with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as serversock:
            serversock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
            serversock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEPORT,True)
            serversock.bind((ip,port))
            serversock.listen(16)
            logging.info(f"异常IO套节字准备完成 http://{ip}:{port}")

            poll.register(serversock.fileno(),select.POLLIN)
            logging.info(f"等待客户端的连接请求")
            serversock_fileno = serversock.fileno()
            connections = {}
            requests = {}
            responses = {}
            addres = {}
            for fileno,event in main_loop(poll):
                if fileno == serversock_fileno:
                    clientsock,client_addr = serversock.accept()
                    logging.info(f"{client_addr[0]}:{client_addr[1]} 发来连接请求")
                    clientsock.setblocking(False)
                    poll.register(clientsock.fileno(),select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
                    connections[clientsock.fileno()] = clientsock
                    requests[clientsock.fileno()] = b''
                    responses[clientsock.fileno()] = rspns
                    addres[clientsock.fileno()]=client_addr

                elif event & select.POLLHUP:
                    addr = addres[fileno]
                    logging.info(f"客户端{addr[0]}:{addr[1]}的连接断开")
                    del connections[fileno]
                    del requests[fileno]
                    del responses[fileno]
                    del addres[fileno]
                    poll.unregister(fileno)
                elif event & select.POLLIN:
                    addr = addres[fileno]
                    logging.info(f"{addr[0]}:{addr[1]} 发来信息")
                    data = connections[fileno].recv(4096)
                    requests[fileno] += data

                    if b'\r\n\r\n' in requests[fileno]:
                        logging.info(f"{addr[0]}:{addr[1]} 发来的信息接收完成")
                        requests[fileno] = b''
                        poll.modify(fileno,select.POLLOUT | select.POLLERR | select.POLLHUP | select.POLLNVAL)
                elif event & select.POLLOUT:
                    addr = addres[fileno]
                    logging.info(f"发送响应报文到 {addr[0]}:{addr[1]}")
                    send_len = connections[fileno].send(responses[fileno])
                    responses[fileno] = responses[fileno][send_len:]
                    if len(responses[fileno]) == 0:
                        poll.modify(fileno,select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
                        responses[fileno] = rspns
                elif event & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
                    logging.info("POLL.OTHER ")
                    connections[fileno].close()
                    del connections[fileno]
                    del requests[fileno]
                    del responses[fileno]
                    del addres[fileno]
                logging.info(f"当前服务端依然保持着 {len(connections)} 个连接")
    except KeyboardInterrupt as err:
        logging.error(str(err))
        sys.exit()
    finally:
        poll.unregister(serversock_fileno)

servers = {
    'block':block_http_server,
    'aio':aio_http_server
}

if __name__ == "__main__":
    parse = argparse.ArgumentParser(name)
    parse.add_argument('--ip',default='127.0.0.1',help='listening ip')
    parse.add_argument('--port',default=8080,type=int,help='listening port')
    parse.add_argument('--message',default='mtls-http is working ...',help='display message')
    parse.add_argument('--server-type',default='aio',help='http server type',choices=('aio','block'))
    args = parse.parse_args()
    servers[args.server_type](args.ip,args.port,args.message)

