def welcome(request: HTTPRequest) -> HTTPResponse: cookie_header = request.headers.get("Cookie", None) # Cookieが送信されてきていなければ、ログインしていないとみなして/loginへリダイレクト if not cookie_header: return HTTPResponse(status_code=302, headers={"Location": "/login"}) # str から list へ変換 # ex) "name1=value1; name2=value2" => ["name1=value1", "name2=value2"] cookie_strings = cookie_header.split("; ") # list から dict へ変換 # ex) ["name1=value1", "name2=value2"] => {"name1": "value1", "name2": "value2"} cookies = {} for cookie_string in cookie_strings: name, value = cookie_string.split("=", maxsplit=1) cookies[name] = value # Cookieにusernameが含まれていなければ、ログインしていないとみなして/loginへリダイレクト if "username" not in cookies: return HTTPResponse(status_code=302, headers={"Location": "/login"}) # Welcome画面を表示 body = render("welcome.html", context={"username": cookies["username"]}) return HTTPResponse(body=body)
def build_response_header(self, response: HTTPResponse, request: HTTPRequest) -> str: """ レスポンスヘッダーを構築する """ # Content-Typeが指定されていない場合はpathから特定する if response.content_type is None: # pathから拡張子を取得 if "." in request.path: ext = request.path.rsplit(".", maxsplit=1)[-1] # 拡張子からMIME Typeを取得 # 知らない対応していない拡張子の場合はoctet-streamとする response.content_type = self.MIME_TYPES.get( ext, "application/octet-stream") else: # pathに拡張子がない場合はhtml扱いとする response.content_type = "text/html; charset=UTF-8" response_header = "" response_header += f"Date: {datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')}\r\n" response_header += "Host: HenaServer/0.1\r\n" response_header += f"Content-Length: {len(response.body)}\r\n" response_header += "Connection: Close\r\n" response_header += f"Content-Type: {response.content_type}\r\n" for cookie_name, cookie_value in response.cookies.items(): response_header += f"Set-Cookie: {cookie_name}={cookie_value}\r\n" for header_name, header_value in response.headers.items(): response_header += f"{header_name}: {header_value}\r\n" return response_header
def static(request: HTTPRequest) -> HTTPResponse: """ 静的ファイルからレスポンスを取得する """ try: static_root = getattr(settings, "STATIC_ROOT") # pathの先頭の/を削除し、相対パスにしておく relative_path = request.path.lstrip("/") # ファイルのpathを取得 static_file_path = os.path.join(static_root, relative_path) with open(static_file_path, "rb") as f: response_body = f.read() content_type = None return HTTPResponse(body=response_body, content_type=content_type, status_code=200) except OSError: # ファイルを取得できなかった場合は、ログを出力して404を返す traceback.print_exc() response_body = b"<html><body><h1>404 Not Found</h1></body></html>" content_type = "text/html;" return HTTPResponse(body=response_body, content_type=content_type, status_code=404)
def run(self) -> None: """ クライアントと接続済みのsocketを引数として受け取り、 リクエストを処理してレスポンスを送信する """ try: # クライアントから送られてきたデータを取得する request_bytes = self.client_socket.recv(4096) # クライアントから送られてきたデータをファイルに書き出す with open("server_recv.txt", "wb") as f: f.write(request_bytes) # HTTPリクエストをパースする request = self.parse_http_request(request_bytes) # pathに対応するview関数があれば、関数を取得して呼び出し、レスポンスを生成する if request.path in URL_VIEW: view = URL_VIEW[request.path] response = view(request) # pathがそれ以外のときは、静的ファイルからレスポンスを生成する else: try: response_body = self.get_static_file_content(request.path) content_type = None response = HTTPResponse(body=response_body, content_type=content_type, status_code=200) except OSError: # レスポンスを取得できなかった場合は、ログを出力して404を返す traceback.print_exc() response_body = b"<html><body><h1>404 Not Found</h1></body></html>" content_type = "text/html;" response = HTTPResponse(body=response_body, content_type=content_type, status_code=404) # レスポンスラインを生成 response_line = self.build_response_line(response) # レスポンスヘッダーを生成 response_header = self.build_response_header(response, request) # レスポンス全体を生成する response_bytes = (response_line + response_header + "\r\n").encode() + response.body # クライアントへレスポンスを送信する self.client_socket.send(response_bytes) except Exception: # リクエストの処理中に例外が発生した場合はコンソールにエラーログを出力し、 # 処理を続行する print("=== Worker: リクエストの処理中にエラーが発生しました ===") traceback.print_exc() finally: # 例外が発生した場合も、発生しなかった場合も、TCP通信のcloseは行う print(f"=== Worker: クライアントとの通信を終了します remote_address: {self.client_address} ===") self.client_socket.close()
def parameters(request: HTTPRequest) -> HTTPResponse: """ POSTパラメータを表示するHTMLを表示する """ if request.method == "GET": body = b"<html><body><h1>405 Method Not Allowed</h1></body></html>" return HTTPResponse(body=body, status_code=405) elif request.method == "POST": post_params = urllib.parse.parse_qs(request.body.decode()) context = {"post_params": post_params} body = render("params.html", context) return HTTPResponse(body=body)
def build_response_header(self, response: HTTPResponse, request: HTTPRequest) -> str: """ レスポンスヘッダーを構築する """ # Content-Typeが指定されていない場合はpathから特定する if response.content_type is None: # pathから拡張子を取得 if "." in request.path: ext = request.path.split(".", maxsplit=1)[-1] # 拡張子からMIME Typeを取得 # 知らない対応していない拡張子の場合はoctet-streamとする response.content_type = self.MIME_TYPE.get( ext, "application/octet-stream") else: # pathに拡張子がない場合はhtml扱いとする response.content_type = "text/html; charset=UTF-8" # 基本ヘッダーを生成 response_header = "" response_header += f"Date: {datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')}\r\n" response_header += "Host: HenaServer//0.1\r\n" response_header += f"Content-Length: {len(response.body)}\r\n" response_header += "Connection: Close\r\n" response_header += f"Content-Type: {response.content_type}\r\n" # Cookieヘッダーの生成 for cookie in response.cookies: cookie_header = f"Set-Cookie: {cookie.name}={cookie.value}" if cookie.expires is not None: cookie_header += f"; Expires={cookie.expires.strftime('%a, %d %b %Y %H:%M:%S GMT')}" if cookie.max_age is not None: cookie_header += f"; Max-Age={cookie.max_age}" if cookie.domain: cookie_header += f"; Domain={cookie.domain}" if cookie.path: cookie_header += f"; Path={cookie.path}" if cookie.secure: cookie_header += f"; Secure" if cookie.http_only: cookie_header += f"; HttpOnly" response_header += cookie_header + "\r\n" # その他のヘッダーの生成 for header_name, header_value in response.headers.items(): response_header += f"{header_name}: {header_value}\r\n" return response_header
def login(request: HTTPRequest) -> HTTPResponse: if request.method == "GET": body = render("login.html", {}) return HTTPResponse(body=body) elif request.method == "POST": post_params = urllib.parse.parse_qs(request.body.decode()) username = post_params["username"][0] headers = { "Location": "/welcome", "Set-Cookie": f"username={username}" } return HTTPResponse(status_code=302, headers=headers)
def welcome(request: HTTPRequest) -> HTTPResponse: # Cookieにusernameが含まれていなければ、ログインしていないとみなして/loginへリダイレクト if "username" not in request.cookies: return HTTPResponse(status_code=302, headers={"Location": "/login"}) # Welcome画面を表示 username = request.cookies["username"] email = request.cookies["email"] body = render("welcome.html", context={ "username": username, "email": email }) return HTTPResponse(body=body)
def build_response_header(self, response: HTTPResponse, request: HTTPRequest) -> str: """ レスポンスヘッダーを構築する """ # Content-Typeが指定されていない場合はpathから特定する if response.content_type is None: # pathから拡張子を取得 if "." in request.path: ext = request.path.rsplit(".", maxsplit=1)[-1] else: ext = "" # 拡張子からMIME Typeを取得 # 知らない対応していない拡張子の場合はoctet-streamとする response.content_type = self.MIME_TYPES.get( ext, "application/octet-stream") response_header = "" response_header += f"Date: {datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')}\r\n" response_header += "Host: HenaServer/0.1\r\n" response_header += f"Content-Length: {len(response.body)}\r\n" response_header += "Connection: Close\r\n" response_header += f"Content-Type: {response.content_type}\r\n" return response_header
def parameters(request: HTTPRequest) -> HTTPResponse: """ POSTパラメータを表示するHTMLを表示する """ # GETリクエストの場合は、405を返す if request.method == "GET": body = b"<html><body><h1>405 Method Not Allowed</h1></body></html>" content_type = "text/html; charset=UTF-8" status_code = 405 elif request.method == "POST": post_params = urllib.parse.parse_qs(request.body.decode()) html = f"""\ <html> <body> <h1>Parameters:</h1> <pre>{pformat(post_params)}</pre> </body> </html> """ body = textwrap.dedent(html).encode() content_type = "text/html; charset=UTF-8" status_code = 200 return HTTPResponse(body=body, content_type=content_type, status_code=status_code)
def now(request: HTTPRequest) -> HTTPResponse: """ 現在時刻を表示するHTMLを生成する """ context = {"now": datetime.now()} body = render("now.html", context) return HTTPResponse(body=body)
def login(request: HTTPRequest) -> HTTPResponse: if request.method == "GET": body = render("login.html", {}) return HTTPResponse(body=body) elif request.method == "POST": post_params = urllib.parse.parse_qs(request.body.decode()) username = post_params["username"][0] email = post_params["email"][0] cookies = [ Cookie(name="username", value=username, max_age=30), Cookie(name="email", value=email, max_age=30), ] return HTTPResponse(status_code=302, headers={"Location": "/welcome"}, cookies=cookies)
def now(request: HTTPRequest) -> HTTPResponse: """ 現在時刻を表示するHTMLを生成する """ context = {"now": datetime.now()} body = render("now.html", context) content_type = "text/html; charset=UTF-8" return HTTPResponse(body=body, content_type=content_type, status_code=200)
def show_request(request: HTTPRequest) -> HTTPResponse: """ HTTPリクエストの内容を表示するHTMLを生成する """ context = { "request": request, "headers": pformat(request.headers), "body": request.body.decode("utf-8", "ignore") } body = render("show_request.html", context) return HTTPResponse(body=body)
def now(request: HTTPRequest) -> HTTPResponse: """ 現在時刻を表示するHTMLを生成する """ with open("./templates/now.html") as f: template = f.read() html = template.format(now=datetime.now()) body = textwrap.dedent(html).encode() content_type = "text/html; charset=UTF-8" return HTTPResponse(body=body, content_type=content_type, status_code=200)
def now(request: HTTPRequest) -> HTTPResponse: """ 現在時刻を表示するHTMLを生成する """ html = f"""\ <html> <body> <h1>Now: {datetime.now()}</h1> </body> </html> """ body = textwrap.dedent(html).encode() content_type = "text/html; charset=UTF-8" return HTTPResponse(body=body, content_type=content_type, status_code=200)
def user_profile(request: HTTPRequest) -> HTTPResponse: user_id = request.params["user_id"] html = f""" <html> <body> <h1>プロフィール</h1> <p>ID : {user_id} </body> </html> """ body = textwrap.dedent(html).encode() content_type = "text/html; charset=UTF-8" status_code = 200 return HTTPResponse(body=body, content_type=content_type, status_code=status_code)
def show_request(request: HTTPRequest) -> HTTPResponse: """ HTTPリクエストの内容を表示するHTMLを生成する """ html = f"""\ <html> <body> <h1>Request Line:</h1> <p> {request.method} {request.path} {request.http_version} </p> <h1>Headers:</h1>Ò <pre>{pformat(request.headers)}</pre> <h1>Body:</h1> <pre>{request.body.decode("utf-8", "ignore")}</pre> </body> </html> """ body = textwrap.dedent(html).encode() return HTTPResponse(body=body)
def set_cookie(request: HTTPRequest) -> HTTPResponse: return HTTPResponse(cookies=[Cookie(name="username", value="TARO")])
def user_profile(request: HTTPRequest) -> HTTPResponse: context = {"user_id": request.params["user_id"]} body = render("user_profile.html", context) return HTTPResponse(body=body)
def set_cookie(request: HTTPRequest) -> HTTPResponse: return HTTPResponse(cookies={"username": "******"})
def set_cookie(request: HTTPRequest) -> HTTPResponse: return HTTPResponse(headers={"Set-Cookie": "username=TARO"})