Parses HTTP request parameters (a list of lines) into a dict.
\n\n
The parsed request dict contains one key/value pair per line, with the \ndict key being the left part of the line (the parameter key), and the \ndict value being the right part of the line (the parameter value).
\n\n
The function strips leading and trailing spaces: \" Host: a.org \" becomes\n{\"Host\": \"a.org\"}.
\n\n
Parameters
\n\n
\n
lines: list[str]\nHTTP parameters (one list item per line)
\n
\n\n
Returns
\n\n
dict[str, str]\n Dictionary of the parameters
\n\n
Raises
\n\n
ValueError\n The provided lines are not valid HTTP.
dict \n Information about the HTTP code, containing fileds:\n - header: str \n The code string to put in an HTTP reply header.\n - html: str \n The HTML to reply as HTTP content.
Parses HTTP request parameters (a list of lines) into a dict.
\n\n
The parsed request dict contains one key/value pair per line, with the \ndict key being the left part of the line (the parameter key), and the \ndict value being the right part of the line (the parameter value).
\n\n
The function strips leading and trailing spaces: \" Host: a.org \" becomes\n{\"Host\": \"a.org\"}.
\n\n
Parameters
\n\n
\n
lines: list[str]\nHTTP parameters (one list item per line)
\n
\n\n
Returns
\n\n
dict[str, str]\n Dictionary of the parameters
\n\n
Raises
\n\n
ValueError\n The provided lines are not valid HTTP.
Retrieves the content of the resource and sets the status code.
\n\n
Parameters
\n\n
\n
root: str\nThe path to the local directory to serve.
\n
req: dict[str, dict]\nThe request to proceed.
\n
\n\n
Returns
\n\n
tuple \n The reply for the request, including the data and status code.\n - data: str\n The data (header + content) to reply on the socket.\n - code: int\n The status code for the reply.
content_type: str\nThe content type for the resource.
\n
code: int\nThe status code.
\n
\n\n
Returns
\n\n
tuple \n The reply for the request, including the data and status code.\n - data: str\n The data (header + content) to reply on the socket.\n - code: int\n The status code for the reply.
\n", "signature": "(content:bytes, content_type:str, code:int):", "funcdef": "def"}];
// mirrored in build-search-index.js (part 1)
// Also split on html tags. this is a cheap heuristic, but good enough.
diff --git a/scripts/03_server/docs/src/myserver.html b/scripts/03_server/docs/src/myserver.html
index 29dcaca..495b6a9 100644
--- a/scripts/03_server/docs/src/myserver.html
+++ b/scripts/03_server/docs/src/myserver.html
@@ -32,6 +32,7 @@
1######################################################################
+ 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
+ 3#
+ 4# This program and the accompanying materials are made
+ 5# available under the terms of the Eclipse Public License 2.0
+ 6# which is available at https://www.eclipse.org/legal/epl-2.0/
+ 7#
+ 8# SPDX-License-Identifier: EPL-2.0
+ 9######################################################################
+10
+11"""
+12A package for learning network programming in Python.
+13
+14This module (file) defines the directory as a Python module.
+15"""
A module for learning network programming in Python.
+
A package for learning network programming in Python.
-
This functions manage the command line interface to control the server.
+
This module (file) manages the command line interface to control the server.
@@ -72,9 +72,9 @@
9######################################################################1011"""
-12A module for learning network programming in Python.
+12A package for learning network programming in Python.13
-14This functions manage the command line interface to control the server.
+14This module (file) manages the command line interface to control the server.15"""1617importargparse
diff --git a/scripts/03_server/docs/src/myserver/date.html b/scripts/03_server/docs/src/myserver/date.html
index edd17e2..7042c28 100644
--- a/scripts/03_server/docs/src/myserver/date.html
+++ b/scripts/03_server/docs/src/myserver/date.html
@@ -49,17 +49,37 @@
1######################################################################
+ 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
+ 3#
+ 4# This program and the accompanying materials are made
+ 5# available under the terms of the Eclipse Public License 2.0
+ 6# which is available at https://www.eclipse.org/legal/epl-2.0/
+ 7#
+ 8# SPDX-License-Identifier: EPL-2.0
+ 9######################################################################
+10
+11"""
+12A package for learning network programming in Python.
+13
+14This module (file) manages date related-utilities.
+15"""
+16
+17fromtimeimportgmtime,strftime
+18
+19_RFC2616_DATE_FORMAT='%a, %d %b %Y %H:%M:%S GMT'
+20
+21defnow_rfc2616():
+22returnstrftime(_RFC2616_DATE_FORMAT,gmtime())
1######################################################################
+ 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
+ 3#
+ 4# This program and the accompanying materials are made
+ 5# available under the terms of the Eclipse Public License 2.0
+ 6# which is available at https://www.eclipse.org/legal/epl-2.0/
+ 7#
+ 8# SPDX-License-Identifier: EPL-2.0
+ 9######################################################################
+ 10
+ 11"""
+ 12A package for learning network programming in Python.
+ 13
+ 14This module (file) manages operations relative to the file system.
+ 15"""
+ 16
+ 17importos.pathaspath
+ 18
+ 19
+ 20defresolve_location(res:str,root:str):
+ 21"""Returns the path of a resource relative to the root and its extension.
+ 22
+ 23 Returns ("", "") if the concatenated path does not exist.
+ 24
+ 25 "index.html" is appended to directory paths.
+ 26
+ 27 Parameters
+ 28 ----------
+ 29 res: str
+ 30 The queried resource path.
+ 31 root: str
+ 32 The root directory where to look into for res.
+ 33
+ 34 Returns
+ 35 -------
+ 36 str
+ 37 The full disk path of the resource if it exists, or "".
+ 38 str
+ 39 The extension of the resource if it exists, or "".
+ 40 """
+ 41try:
+ 42res_path=resolve_path(res,root)
+ 43
+ 44# Compute file extension
+ 45extension=path.splitext(res_path)[1]
+ 46# Maybe remove leading '.'
+ 47iflen(extension)>0andextension[0]=='.':
+ 48extension=extension[1:]
+ 49
+ 50returnres_path,extension
+ 51exceptException:
+ 52return"",""
+ 53
+ 54defresolve_path(res:str,root:str):
+ 55"""Returns the full disk path of a resource relative to the root.
+ 56
+ 57 Returns "" if the concatenated path does not exist.
+ 58
+ 59 "index.html" is appended to directory paths.
+ 60
+ 61 Parameters
+ 62 ----------
+ 63 res: str
+ 64 The queried resource path.
+ 65 root: str
+ 66 The root directory where to look into for res.
+ 67
+ 68 Returns
+ 69 -------
+ 70 str
+ 71 The full disk path of the resource if it exists, or "".
+ 72 """
+ 73try:
+ 74# Resolve the home directory if it is in the root
+ 75root=path.expanduser(root)
+ 76
+ 77ifres[-1]=="/":
+ 78res+="index.html"
+ 79whileres[0]=="/":
+ 80res=res[1:]
+ 81
+ 82res_path=path.join(root,res)
+ 83
+ 84ifnot(path.exists(res_path)andpath.isfile(res_path)):
+ 85return""
+ 86returnres_path
+ 87exceptException:
+ 88return""
+ 89
+ 90
+ 91
+ 92defget_resource(res_path:str):
+ 93"""Returns a resource at res_path, its content type and an HTTP code.
+ 94
+ 95 Parameters
+ 96 ----------
+ 97 - res_path: str
+ 98 Requested resource string.
+ 99
+100 Returns
+101 -------
+102 bytes
+103 The resource content if it exists (code == 200).
+104 int
+105 A HTTP status code.
+106 """
+107
+108
+109print(f"Opening file {res_path}...")
+110withopen(res_path,'rb')asf:
+111content=f.read()
+112returncontent,200
22defget_resource(res:str,root:str):
-23"""Returns the resource requested by a GET request.
-24
-25 Parameters
-26 ----------
-27 - res: str
-28 Requested resource string.
-29 - root: str
-30 Root directory of the server.
-31
-32 Returns
-33 -------
-34 int
-35 The response HTTP code.
-36 bytes
-37 The resource content.
-38 """
-39
-40code,res_path=resolve_location(res,root)
-41ifcode==404:
-42return404,_404_CONTENT
-43elifcode!=200:
-44return500,_500_CONTENT
-45
-46print(f"Opening file {res_path}...")
-47withopen(res_path,'rb')asf:
-48content=f.read()
-49return200,content
-
-
-
-
Returns the resource requested by a GET request.
-
-
Parameters
-
-
-
res: str
-Requested resource string.
-
root: str
-Root directory of the server.
-
-
-
Returns
-
-
int
- The response HTTP code.
-bytes
- The resource content.
-
-
-
-
@@ -204,29 +191,187 @@ bytes
-
52defresolve_location(res:str,root:str):
-53assertlen(res)!=0
-54
-55# Resolve the home directory if it is in the root
-56root=path.expanduser(root)
-57
-58ifres[-1]=="/":
-59res+="index.html"
-60ifres[0]=="/":
-61res=res[1:]
-62
-63res_path=path.join(root,res)
-64
-65ifnot(path.exists(res_path)andpath.isfile(res_path)):
-66return404,""
-67# if path.isdir(res_path):
-68# if not path.exists(res_path):
-69# return 404, _404_CONTENT
-70return200,res_path
+
21defresolve_location(res:str,root:str):
+22"""Returns the path of a resource relative to the root and its extension.
+23
+24 Returns ("", "") if the concatenated path does not exist.
+25
+26 "index.html" is appended to directory paths.
+27
+28 Parameters
+29 ----------
+30 res: str
+31 The queried resource path.
+32 root: str
+33 The root directory where to look into for res.
+34
+35 Returns
+36 -------
+37 str
+38 The full disk path of the resource if it exists, or "".
+39 str
+40 The extension of the resource if it exists, or "".
+41 """
+42try:
+43res_path=resolve_path(res,root)
+44
+45# Compute file extension
+46extension=path.splitext(res_path)[1]
+47# Maybe remove leading '.'
+48iflen(extension)>0andextension[0]=='.':
+49extension=extension[1:]
+50
+51returnres_path,extension
+52exceptException:
+53return"",""
-
+
Returns the path of a resource relative to the root and its extension.
+
+
Returns ("", "") if the concatenated path does not exist.
+
+
"index.html" is appended to directory paths.
+
+
Parameters
+
+
res: str
+ The queried resource path.
+root: str
+ The root directory where to look into for res.
+
+
Returns
+
+
str
+ The full disk path of the resource if it exists, or "".
+str
+ The extension of the resource if it exists, or "".
55defresolve_path(res:str,root:str):
+56"""Returns the full disk path of a resource relative to the root.
+57
+58 Returns "" if the concatenated path does not exist.
+59
+60 "index.html" is appended to directory paths.
+61
+62 Parameters
+63 ----------
+64 res: str
+65 The queried resource path.
+66 root: str
+67 The root directory where to look into for res.
+68
+69 Returns
+70 -------
+71 str
+72 The full disk path of the resource if it exists, or "".
+73 """
+74try:
+75# Resolve the home directory if it is in the root
+76root=path.expanduser(root)
+77
+78ifres[-1]=="/":
+79res+="index.html"
+80whileres[0]=="/":
+81res=res[1:]
+82
+83res_path=path.join(root,res)
+84
+85ifnot(path.exists(res_path)andpath.isfile(res_path)):
+86return""
+87returnres_path
+88exceptException:
+89return""
+
+
+
+
Returns the full disk path of a resource relative to the root.
+
+
Returns "" if the concatenated path does not exist.
+
+
"index.html" is appended to directory paths.
+
+
Parameters
+
+
res: str
+ The queried resource path.
+root: str
+ The root directory where to look into for res.
+
+
Returns
+
+
str
+ The full disk path of the resource if it exists, or "".
+
+
+
+
+
+
+
+
+ def
+ get_resource(res_path:str):
+
+
+
+
+
+
93defget_resource(res_path:str):
+ 94"""Returns a resource at res_path, its content type and an HTTP code.
+ 95
+ 96 Parameters
+ 97 ----------
+ 98 - res_path: str
+ 99 Requested resource string.
+100
+101 Returns
+102 -------
+103 bytes
+104 The resource content if it exists (code == 200).
+105 int
+106 A HTTP status code.
+107 """
+108
+109
+110print(f"Opening file {res_path}...")
+111withopen(res_path,'rb')asf:
+112content=f.read()
+113returncontent,200
+
+
+
+
Returns a resource at res_path, its content type and an HTTP code.
+
+
Parameters
+
+
+
res_path: str
+Requested resource string.
+
+
+
Returns
+
+
bytes
+ The resource content if it exists (code == 200).
+int
+ A HTTP status code.
dict
+ Information about the HTTP code, containing fileds:
+ - header: str
+ The code string to put in an HTTP reply header.
+ - html: str
+ The HTML to reply as HTTP content.
A module for learning network programming in Python.
+
A package for learning network programming in Python.
-
This functions handle the http messages.
+
This module (file) manages the parsing of HTTP requests.
@@ -75,120 +75,122 @@
9###################################################################### 10 11"""
- 12A module for learning network programming in Python.
+ 12A package for learning network programming in Python. 13
- 14This functions handle the http messages.
+ 14This module (file) manages the parsing of HTTP requests. 15""" 16
- 17defparse_request(buf:bytes)->dict[str,dict]:
- 18"""Parses a full HTTP request bytes buffer into a dict.
- 19
- 20 The parsed request dict contains two keys:
- 21 - head: dict[str, str]
- 22 Information on the HTTP request header (i.e. the first request line);
- 23 output of `parse_request_head`.
- 24 - params: dict[str, str]
- 25 List of the HTTP parameters (i.e. the following lines);
- 26 output of `parse_request_params`.
- 27
- 28 Parameters
- 29 ----------
- 30 - buf: bytes
- 31 The HTTP request buffer.
- 32
- 33 Returns
- 34 -------
- 35 dict[str, dict]
- 36 The parsed content of the HTTP request.
- 37
- 38 Raises
- 39 ------
- 40 ValueError
- 41 The request is not valid HTTP.
- 42 """
- 43ifbuf==b'':
- 44raiseValueError("Received empty request")
- 45lines=buf.decode('utf-8').strip().splitlines()
- 46
- 47req_head=parse_request_head(lines[0])
- 48req_params=dict()
- 49iflen(lines)>1:
- 50req_params=parse_request_params(lines[1:])
- 51
- 52returndict(
- 53head=req_head,
- 54params=req_params
- 55)
- 56
- 57defparse_request_head(line:str)->dict[str,str]:
- 58"""Parses a HTTP request header string (its first line) into a dict.
- 59
- 60 The parsed request dict contains two keys:
- 61 - verb: str
- 62 The _uppercase_ verb of the request, i.e. the first word of the line;
- 63 for example: "GET".
- 64 - resource: str
- 65 The requested resource, i.e. the second "word" of the line;
- 66 for example: "/index.html".
- 67
- 68 Parameters
- 69 ----------
- 70 - line: str
- 71 The HTTP request header (the first line of a full HTTP request).
- 72
- 73 Returns
- 74 -------
- 75 dict[str, str]
- 76 The parsed content of the HTTP request header.
- 77
- 78 Raises
- 79 ------
- 80 ValueError
- 81 The request header is not valid HTTP.
- 82 """
- 83fields=line.split(' ')
- 84iflen(fields)!=3:
- 85raiseValueError(f"Request header is invalid: {line}")
- 86
- 87returndict(
- 88verb=fields[0].upper(),
- 89resource=fields[1]
- 90)
- 91
- 92defparse_request_params(lines:list[str])->dict[str,str]:
- 93"""Parses HTTP request parameters (a list of lines) into a dict.
- 94
- 95 The parsed request dict contains one key/value pair per line, with the
- 96 dict key being the left part of the line (the parameter key), and the
- 97 dict value being the right part of the line (the parameter value).
- 98
- 99 The function strips leading and trailing spaces: " Host: a.org " becomes
-100 `{"Host": "a.org"}`.
-101
-102 Parameters
-103 ----------
-104 - lines: list[str]
-105 HTTP parameters (one list item per line)
-106
-107 Returns
-108 -------
-109 dict[str, str]
-110 Dictionary of the parameters
-111
-112 Raises
-113 ------
-114 ValueError
-115 The provided lines are not valid HTTP.
-116 """
-117params=dict()
-118forlinlines:
-119kv=l.strip().split(': ')
-120
-121iflen(kv)!=2orlen(kv[0])==0orlen(kv[1])==0:
-122raiseValueError(f"Request line is not a valid key/value pair: {l}")
-123
-124params[kv[0]]=kv[1]
-125returnparams
+ 17# TODO: Gestion de la casse des paramètres ?
+ 18
+ 19defparse_request(buf:bytes)->dict[str,dict]:
+ 20"""Parses a full HTTP request bytes buffer into a dict.
+ 21
+ 22 The parsed request dict contains two keys:
+ 23 - head: dict[str, str]
+ 24 Information on the HTTP request header (i.e. the first request line);
+ 25 output of `parse_request_head`.
+ 26 - params: dict[str, str]
+ 27 List of the HTTP parameters (i.e. the following lines);
+ 28 output of `parse_request_params`.
+ 29
+ 30 Parameters
+ 31 ----------
+ 32 - buf: bytes
+ 33 The HTTP request buffer.
+ 34
+ 35 Returns
+ 36 -------
+ 37 dict[str, dict]
+ 38 The parsed content of the HTTP request.
+ 39
+ 40 Raises
+ 41 ------
+ 42 ValueError
+ 43 The request is not valid HTTP.
+ 44 """
+ 45ifbuf==b'':
+ 46raiseValueError("Received empty request")
+ 47lines=buf.decode('utf-8').strip().splitlines()
+ 48
+ 49req_head=parse_request_head(lines[0])
+ 50req_params=dict()
+ 51iflen(lines)>1:
+ 52req_params=parse_request_params(lines[1:])
+ 53
+ 54returndict(
+ 55head=req_head,
+ 56params=req_params
+ 57)
+ 58
+ 59defparse_request_head(line:str)->dict[str,str]:
+ 60"""Parses a HTTP request header string (its first line) into a dict.
+ 61
+ 62 The parsed request dict contains two keys:
+ 63 - verb: str
+ 64 The _uppercase_ verb of the request, i.e. the first word of the line;
+ 65 for example: "GET".
+ 66 - resource: str
+ 67 The requested resource, i.e. the second "word" of the line;
+ 68 for example: "/index.html".
+ 69
+ 70 Parameters
+ 71 ----------
+ 72 - line: str
+ 73 The HTTP request header (the first line of a full HTTP request).
+ 74
+ 75 Returns
+ 76 -------
+ 77 dict[str, str]
+ 78 The parsed content of the HTTP request header.
+ 79
+ 80 Raises
+ 81 ------
+ 82 ValueError
+ 83 The request header is not valid HTTP.
+ 84 """
+ 85fields=line.split(' ')
+ 86iflen(fields)!=3:
+ 87raiseValueError(f"Request header is invalid: {line}")
+ 88
+ 89returndict(
+ 90verb=fields[0].upper(),
+ 91resource=fields[1]
+ 92)
+ 93
+ 94defparse_request_params(lines:list[str])->dict[str,str]:
+ 95"""Parses HTTP request parameters (a list of lines) into a dict.
+ 96
+ 97 The parsed request dict contains one key/value pair per line, with the
+ 98 dict key being the left part of the line (the parameter key), and the
+ 99 dict value being the right part of the line (the parameter value).
+100
+101 The function strips leading and trailing spaces: " Host: a.org " becomes
+102 `{"Host": "a.org"}`.
+103
+104 Parameters
+105 ----------
+106 - lines: list[str]
+107 HTTP parameters (one list item per line)
+108
+109 Returns
+110 -------
+111 dict[str, str]
+112 Dictionary of the parameters
+113
+114 Raises
+115 ------
+116 ValueError
+117 The provided lines are not valid HTTP.
+118 """
+119params=dict()
+120forlinlines:
+121kv=l.strip().split(': ')
+122
+123iflen(kv)!=2orlen(kv[0])==0orlen(kv[1])==0:
+124raiseValueError(f"Request line is not a valid key/value pair: {l}")
+125
+126params[kv[0]]=kv[1]
+127returnparams
@@ -204,45 +206,45 @@
-
18defparse_request(buf:bytes)->dict[str,dict]:
-19"""Parses a full HTTP request bytes buffer into a dict.
-20
-21 The parsed request dict contains two keys:
-22 - head: dict[str, str]
-23 Information on the HTTP request header (i.e. the first request line);
-24 output of `parse_request_head`.
-25 - params: dict[str, str]
-26 List of the HTTP parameters (i.e. the following lines);
-27 output of `parse_request_params`.
-28
-29 Parameters
-30 ----------
-31 - buf: bytes
-32 The HTTP request buffer.
-33
-34 Returns
-35 -------
-36 dict[str, dict]
-37 The parsed content of the HTTP request.
-38
-39 Raises
-40 ------
-41 ValueError
-42 The request is not valid HTTP.
-43 """
-44ifbuf==b'':
-45raiseValueError("Received empty request")
-46lines=buf.decode('utf-8').strip().splitlines()
-47
-48req_head=parse_request_head(lines[0])
-49req_params=dict()
-50iflen(lines)>1:
-51req_params=parse_request_params(lines[1:])
-52
-53returndict(
-54head=req_head,
-55params=req_params
-56)
+
20defparse_request(buf:bytes)->dict[str,dict]:
+21"""Parses a full HTTP request bytes buffer into a dict.
+22
+23 The parsed request dict contains two keys:
+24 - head: dict[str, str]
+25 Information on the HTTP request header (i.e. the first request line);
+26 output of `parse_request_head`.
+27 - params: dict[str, str]
+28 List of the HTTP parameters (i.e. the following lines);
+29 output of `parse_request_params`.
+30
+31 Parameters
+32 ----------
+33 - buf: bytes
+34 The HTTP request buffer.
+35
+36 Returns
+37 -------
+38 dict[str, dict]
+39 The parsed content of the HTTP request.
+40
+41 Raises
+42 ------
+43 ValueError
+44 The request is not valid HTTP.
+45 """
+46ifbuf==b'':
+47raiseValueError("Received empty request")
+48lines=buf.decode('utf-8').strip().splitlines()
+49
+50req_head=parse_request_head(lines[0])
+51req_params=dict()
+52iflen(lines)>1:
+53req_params=parse_request_params(lines[1:])
+54
+55returndict(
+56head=req_head,
+57params=req_params
+58)
@@ -290,40 +292,40 @@ The HTTP request buffer.
-
58defparse_request_head(line:str)->dict[str,str]:
-59"""Parses a HTTP request header string (its first line) into a dict.
-60
-61 The parsed request dict contains two keys:
-62 - verb: str
-63 The _uppercase_ verb of the request, i.e. the first word of the line;
-64 for example: "GET".
-65 - resource: str
-66 The requested resource, i.e. the second "word" of the line;
-67 for example: "/index.html".
-68
-69 Parameters
-70 ----------
-71 - line: str
-72 The HTTP request header (the first line of a full HTTP request).
-73
-74 Returns
-75 -------
-76 dict[str, str]
-77 The parsed content of the HTTP request header.
-78
-79 Raises
-80 ------
-81 ValueError
-82 The request header is not valid HTTP.
-83 """
-84fields=line.split(' ')
-85iflen(fields)!=3:
-86raiseValueError(f"Request header is invalid: {line}")
-87
-88returndict(
-89verb=fields[0].upper(),
-90resource=fields[1]
-91)
+
60defparse_request_head(line:str)->dict[str,str]:
+61"""Parses a HTTP request header string (its first line) into a dict.
+62
+63 The parsed request dict contains two keys:
+64 - verb: str
+65 The _uppercase_ verb of the request, i.e. the first word of the line;
+66 for example: "GET".
+67 - resource: str
+68 The requested resource, i.e. the second "word" of the line;
+69 for example: "/index.html".
+70
+71 Parameters
+72 ----------
+73 - line: str
+74 The HTTP request header (the first line of a full HTTP request).
+75
+76 Returns
+77 -------
+78 dict[str, str]
+79 The parsed content of the HTTP request header.
+80
+81 Raises
+82 ------
+83 ValueError
+84 The request header is not valid HTTP.
+85 """
+86fields=line.split(' ')
+87iflen(fields)!=3:
+88raiseValueError(f"Request header is invalid: {line}")
+89
+90returndict(
+91verb=fields[0].upper(),
+92resource=fields[1]
+93)
@@ -371,40 +373,40 @@ The HTTP request header (the first line of a full HTTP request).
-
93defparse_request_params(lines:list[str])->dict[str,str]:
- 94"""Parses HTTP request parameters (a list of lines) into a dict.
- 95
- 96 The parsed request dict contains one key/value pair per line, with the
- 97 dict key being the left part of the line (the parameter key), and the
- 98 dict value being the right part of the line (the parameter value).
- 99
-100 The function strips leading and trailing spaces: " Host: a.org " becomes
-101 `{"Host": "a.org"}`.
-102
-103 Parameters
-104 ----------
-105 - lines: list[str]
-106 HTTP parameters (one list item per line)
-107
-108 Returns
-109 -------
-110 dict[str, str]
-111 Dictionary of the parameters
-112
-113 Raises
-114 ------
-115 ValueError
-116 The provided lines are not valid HTTP.
-117 """
-118params=dict()
-119forlinlines:
-120kv=l.strip().split(': ')
-121
-122iflen(kv)!=2orlen(kv[0])==0orlen(kv[1])==0:
-123raiseValueError(f"Request line is not a valid key/value pair: {l}")
-124
-125params[kv[0]]=kv[1]
-126returnparams
+
95defparse_request_params(lines:list[str])->dict[str,str]:
+ 96"""Parses HTTP request parameters (a list of lines) into a dict.
+ 97
+ 98 The parsed request dict contains one key/value pair per line, with the
+ 99 dict key being the left part of the line (the parameter key), and the
+100 dict value being the right part of the line (the parameter value).
+101
+102 The function strips leading and trailing spaces: " Host: a.org " becomes
+103 `{"Host": "a.org"}`.
+104
+105 Parameters
+106 ----------
+107 - lines: list[str]
+108 HTTP parameters (one list item per line)
+109
+110 Returns
+111 -------
+112 dict[str, str]
+113 Dictionary of the parameters
+114
+115 Raises
+116 ------
+117 ValueError
+118 The provided lines are not valid HTTP.
+119 """
+120params=dict()
+121forlinlines:
+122kv=l.strip().split(': ')
+123
+124iflen(kv)!=2orlen(kv[0])==0orlen(kv[1])==0:
+125raiseValueError(f"Request line is not a valid key/value pair: {l}")
+126
+127params[kv[0]]=kv[1]
+128returnparams
A module for learning network programming in Python.
+
A package for learning network programming in Python.
-
This functions manage the socket connections and multi-threading of clients.
+
This part manages the socket connections and multi-threading of clients.
-
1######################################################################
- 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
- 3#
- 4# This program and the accompanying materials are made
- 5# available under the terms of the Eclipse Public License 2.0
- 6# which is available at https://www.eclipse.org/legal/epl-2.0/
- 7#
- 8# SPDX-License-Identifier: EPL-2.0
- 9######################################################################
-10
-11"""
-12A module for learning network programming in Python.
-13
-14This functions manage the socket connections and multi-threading of clients.
-15"""
-16
-17importsocket
-18frommyserver.logimportlog,log_reply
-19frommyserver.http_requestimportparse_request
-20frommyserver.fileimportget_resource
-21frommyserver.dateimportnow_rfc2616
-22
-23_BUF_SIZE=1024
-24_SERVER_ADDR="0.0.0.0"
-25
-26defserve(port:int,root:str):
-27"""
-28 Serves http request connections for clients.
-29
-30 This function creates the network socket, listens, and calls
-31 :func:`~myserver.handle_client()` when a request comes in.
-32 """
-33s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
-34
-35# Allows reusing a socket right after it got closed (after ctrl-c)
-36s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
-37
-38s.bind((_SERVER_ADDR,port))
-39s.listen()
-40
-41log(f"Server started at {_SERVER_ADDR}:{port}.")
-42
-43try:# Catch KeyboardInterrupt to close server socket
-44whileTrue:
-45c,addr=s.accept()
-46try:# Catch KeyboardInterrupt to close client handling socket
-47handle_client(c,addr,root)
-48exceptKeyboardInterruptase:
-49c.close()
-50raisee
-51exceptKeyboardInterrupt:
-52s.close()
-53
-54defhandle_client(c:socket.socket,addr:tuple[str,int],root:str):
-55buf=c.recv(_BUF_SIZE)
-56req=parse_request(buf)
-57
-58ifreq['head']['verb']=='GET':
-59code,content=get_resource(req['head']['resource'],root)
-60reply(c,addr,req,code,content)
-61
-62c.close()
-63
-64defreply(c:socket.socket,addr:tuple[str,int],req:dict,code:int,content:bytes):
-65return_message="200 OK"
-66ifcode==404:
-67return_message="404 Not found"
-68
-69# TODO: Change content type depending on actual content type
-70reply_header=f"""HTTP/1.1 {return_message}
-71Content-Type: text/html; charset=utf-8
-72Date: {now_rfc2616()}
-73Content-Length: {len(content)}
-74
-75"""
-76c.send(reply_header.encode())
-77c.send(content)
-78
-79log_reply(addr,req,code)
+
1######################################################################
+ 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
+ 3#
+ 4# This program and the accompanying materials are made
+ 5# available under the terms of the Eclipse Public License 2.0
+ 6# which is available at https://www.eclipse.org/legal/epl-2.0/
+ 7#
+ 8# SPDX-License-Identifier: EPL-2.0
+ 9######################################################################
+ 10
+ 11"""
+ 12A package for learning network programming in Python.
+ 13
+ 14This part manages the socket connections and multi-threading of clients.
+ 15"""
+ 16
+ 17importsocket
+ 18frommyserver.logimportlog,log_reply
+ 19frommyserver.http_requestimportparse_request
+ 20frommyserver.fileimportresolve_location,get_resource
+ 21frommyserver.dateimportnow_rfc2616
+ 22frommyserver.httpimportget_http_code,get_http_content_type
+ 23
+ 24_BUF_SIZE=4096
+ 25_SERVER_ADDR="0.0.0.0"
+ 26
+ 27defserve(port:int,root:str):
+ 28"""
+ 29 Serves http request connections for clients.
+ 30
+ 31 This function creates the network socket, listens, and as soon as it receives
+ 32 a request (connection), calls the :func:`~myserver.handle_client()`.
+ 33 """
+ 34s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+ 35
+ 36# Allows reusing a socket right after it got closed (after ctrl-c)
+ 37s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
+ 38
+ 39# Attach the socket to the port and interface provided.
+ 40s.bind((_SERVER_ADDR,port))
+ 41
+ 42## Start listening on the socket.
+ 43s.listen()
+ 44
+ 45log(f"Server started at {_SERVER_ADDR}:{port}.")
+ 46
+ 47# Try / catch KeyboardInterrupt to close server socket properly if we hit
+ 48# the Control-C sequence to interrupt the server.
+ 49try:
+ 50whileTrue:
+ 51c,addr=s.accept()
+ 52# Try / catch KeyboardInterrupt to properly close the client socket if
+ 53# we hit the Control-C sequence to interrupt the server.
+ 54try:
+ 55handle_client(c,addr,root)
+ 56# If the KeyboardInterrupt is raised, we pass it to the outer loop
+ 57# to also close the server socket.
+ 58exceptKeyboardInterruptase:
+ 59c.close()
+ 60raisee
+ 61exceptKeyboardInterrupt:
+ 62log("Received KeyboardInterrupt. Closing...")
+ 63s.close()
+ 64
+ 65
+ 66defhandle_client(c:socket.socket,addr:tuple[str,int],root:str):
+ 67"""
+ 68 Manages a single connection from a client.
+ 69
+ 70 In details, we:
+ 71 * read data from the socket provided,
+ 72 * parse this data to build the request and headers,
+ 73 * call the handle_request() function,
+ 74 [* optionally write something in the log,]
+ 75 * close the connection.
+ 76
+ 77 Parameters
+ 78 ----------
+ 79 - c: socket.socket
+ 80 The socket to communicate with the client.
+ 81 - addr: tuple[str, int]
+ 82 The IP address and port of the client, as returned by the accept command.
+ 83 - root: str
+ 84 The path to the local directory to serve.
+ 85
+ 86 """
+ 87buf=c.recv(_BUF_SIZE)
+ 88req=parse_request(buf)
+ 89
+ 90# Prepare our reply.
+ 91ifreq['head']['verb']=='GET':
+ 92reply,code=prepare_resource(root,req)
+ 93else:
+ 94# Not implemented: we treat only GET calls for now.
+ 95reply,code=prepare_reply(b"","",501)
+ 96
+ 97# Send the reply back.
+ 98c.send(reply)
+ 99
+100# Trace the action in the logs.
+101log_reply(addr,req,code)
+102
+103# Close the connection.
+104c.close()
+105
+106
+107defprepare_resource(root:str,req:dict):
+108"""
+109 Retrieves the content of the resource and sets the status code.
+110
+111 Parameters
+112 ----------
+113 - root: str
+114 The path to the local directory to serve.
+115 - req: dict[str, dict]
+116 The request to proceed.
+117
+118 Returns
+119 -------
+120 tuple
+121 The reply for the request, including the data and status code.
+122 - data: str
+123 The data (header + content) to reply on the socket.
+124 - code: int
+125 The status code for the reply.
+126 """
+127code=200
+128content=b""
+129content_type=""
+130
+131res_path,res_extension=resolve_location(req['head']['resource'],root)
+132ifres_path=="":
+133code=404
+134else:
+135content_type=get_http_content_type(res_extension)
+136content,code=get_resource(res_path)
+137
+138returnprepare_reply(content,content_type,code)
+139
+140
+141defprepare_reply(content:bytes,content_type:str,code:int):
+142"""
+143 Generates the proper answer, including the HTTP headers and content of the
+144 webpage, and the status code.
+145
+146 For more information about:
+147 * Content type, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
+148 * Status code, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
+149
+150 Parameters
+151 ----------
+152 - content: bytes
+153 The raw data for the resource.
+154 - content_type: str
+155 The content type for the resource.
+156 - code: int
+157 The status code.
+158
+159 Returns
+160 -------
+161 tuple
+162 The reply for the request, including the data and status code.
+163 - data: str
+164 The data (header + content) to reply on the socket.
+165 - code: int
+166 The status code for the reply.
+167 """
+168# Prepare status code
+169http_code_dict=get_http_code(code)
+170ifcode!=200:
+171content=http_code_dict['html'].encode()
+172content_type=get_http_content_type('html')+"; charset=utf-8"
+173
+174# Prepare header
+175header=f"""HTTP/1.0 {http_code_dict['header']}
+176Content-Type: {content_type}
+177Date: {now_rfc2616()}
+178Content-Length: {len(content)}
+179Server: RegardeMamanJeFaisUnServeurWeb/0.1
+180
+181""".encode()
+182
+183returnheader+content,code
@@ -158,40 +265,50 @@
-
27defserve(port:int,root:str):
-28"""
-29 Serves http request connections for clients.
-30
-31 This function creates the network socket, listens, and calls
-32 :func:`~myserver.handle_client()` when a request comes in.
-33 """
-34s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
-35
-36# Allows reusing a socket right after it got closed (after ctrl-c)
-37s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
-38
-39s.bind((_SERVER_ADDR,port))
-40s.listen()
-41
-42log(f"Server started at {_SERVER_ADDR}:{port}.")
-43
-44try:# Catch KeyboardInterrupt to close server socket
-45whileTrue:
-46c,addr=s.accept()
-47try:# Catch KeyboardInterrupt to close client handling socket
-48handle_client(c,addr,root)
-49exceptKeyboardInterruptase:
-50c.close()
-51raisee
-52exceptKeyboardInterrupt:
-53s.close()
+
28defserve(port:int,root:str):
+29"""
+30 Serves http request connections for clients.
+31
+32 This function creates the network socket, listens, and as soon as it receives
+33 a request (connection), calls the :func:`~myserver.handle_client()`.
+34 """
+35s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
+36
+37# Allows reusing a socket right after it got closed (after ctrl-c)
+38s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
+39
+40# Attach the socket to the port and interface provided.
+41s.bind((_SERVER_ADDR,port))
+42
+43## Start listening on the socket.
+44s.listen()
+45
+46log(f"Server started at {_SERVER_ADDR}:{port}.")
+47
+48# Try / catch KeyboardInterrupt to close server socket properly if we hit
+49# the Control-C sequence to interrupt the server.
+50try:
+51whileTrue:
+52c,addr=s.accept()
+53# Try / catch KeyboardInterrupt to properly close the client socket if
+54# we hit the Control-C sequence to interrupt the server.
+55try:
+56handle_client(c,addr,root)
+57# If the KeyboardInterrupt is raised, we pass it to the outer loop
+58# to also close the server socket.
+59exceptKeyboardInterruptase:
+60c.close()
+61raisee
+62exceptKeyboardInterrupt:
+63log("Received KeyboardInterrupt. Closing...")
+64s.close()
Serves http request connections for clients.
-
This function creates the network socket, listens, and calls
-~myserver.handle_client()() when a request comes in.
+
This function creates the network socket, listens, and as soon as it receives
+a request (connection), calls the ~myserver.handle_client()().
67defhandle_client(c:socket.socket,addr:tuple[str,int],root:str):
+ 68"""
+ 69 Manages a single connection from a client.
+ 70
+ 71 In details, we:
+ 72 * read data from the socket provided,
+ 73 * parse this data to build the request and headers,
+ 74 * call the handle_request() function,
+ 75 [* optionally write something in the log,]
+ 76 * close the connection.
+ 77
+ 78 Parameters
+ 79 ----------
+ 80 - c: socket.socket
+ 81 The socket to communicate with the client.
+ 82 - addr: tuple[str, int]
+ 83 The IP address and port of the client, as returned by the accept command.
+ 84 - root: str
+ 85 The path to the local directory to serve.
+ 86
+ 87 """
+ 88buf=c.recv(_BUF_SIZE)
+ 89req=parse_request(buf)
+ 90
+ 91# Prepare our reply.
+ 92ifreq['head']['verb']=='GET':
+ 93reply,code=prepare_resource(root,req)
+ 94else:
+ 95# Not implemented: we treat only GET calls for now.
+ 96reply,code=prepare_reply(b"","",501)
+ 97
+ 98# Send the reply back.
+ 99c.send(reply)
+100
+101# Trace the action in the logs.
+102log_reply(addr,req,code)
+103
+104# Close the connection.
+105c.close()
-
+
Manages a single connection from a client.
+
+
In details, we:
+
+
+
read data from the socket provided,
+
parse this data to build the request and headers,
+
call the handle_request() function,
+[* optionally write something in the log,]
+
close the connection.
+
+
+
Parameters
+
+
+
c: socket.socket
+The socket to communicate with the client.
+
addr: tuple[str, int]
+The IP address and port of the client, as returned by the accept command.
+
root: str
+The path to the local directory to serve.
65defreply(c:socket.socket,addr:tuple[str,int],req:dict,code:int,content:bytes):
-66return_message="200 OK"
-67ifcode==404:
-68return_message="404 Not found"
-69
-70# TODO: Change content type depending on actual content type
-71reply_header=f"""HTTP/1.1 {return_message}
-72Content-Type: text/html; charset=utf-8
-73Date: {now_rfc2616()}
-74Content-Length: {len(content)}
-75
-76"""
-77c.send(reply_header.encode())
-78c.send(content)
-79
-80log_reply(addr,req,code)
+
+
108defprepare_resource(root:str,req:dict):
+109"""
+110 Retrieves the content of the resource and sets the status code.
+111
+112 Parameters
+113 ----------
+114 - root: str
+115 The path to the local directory to serve.
+116 - req: dict[str, dict]
+117 The request to proceed.
+118
+119 Returns
+120 -------
+121 tuple
+122 The reply for the request, including the data and status code.
+123 - data: str
+124 The data (header + content) to reply on the socket.
+125 - code: int
+126 The status code for the reply.
+127 """
+128code=200
+129content=b""
+130content_type=""
+131
+132res_path,res_extension=resolve_location(req['head']['resource'],root)
+133ifres_path=="":
+134code=404
+135else:
+136content_type=get_http_content_type(res_extension)
+137content,code=get_resource(res_path)
+138
+139returnprepare_reply(content,content_type,code)
-
+
Retrieves the content of the resource and sets the status code.
+
+
Parameters
+
+
+
root: str
+The path to the local directory to serve.
+
req: dict[str, dict]
+The request to proceed.
+
+
+
Returns
+
+
tuple
+ The reply for the request, including the data and status code.
+ - data: str
+ The data (header + content) to reply on the socket.
+ - code: int
+ The status code for the reply.
142defprepare_reply(content:bytes,content_type:str,code:int):
+143"""
+144 Generates the proper answer, including the HTTP headers and content of the
+145 webpage, and the status code.
+146
+147 For more information about:
+148 * Content type, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
+149 * Status code, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
+150
+151 Parameters
+152 ----------
+153 - content: bytes
+154 The raw data for the resource.
+155 - content_type: str
+156 The content type for the resource.
+157 - code: int
+158 The status code.
+159
+160 Returns
+161 -------
+162 tuple
+163 The reply for the request, including the data and status code.
+164 - data: str
+165 The data (header + content) to reply on the socket.
+166 - code: int
+167 The status code for the reply.
+168 """
+169# Prepare status code
+170http_code_dict=get_http_code(code)
+171ifcode!=200:
+172content=http_code_dict['html'].encode()
+173content_type=get_http_content_type('html')+"; charset=utf-8"
+174
+175# Prepare header
+176header=f"""HTTP/1.0 {http_code_dict['header']}
+177Content-Type: {content_type}
+178Date: {now_rfc2616()}
+179Content-Length: {len(content)}
+180Server: RegardeMamanJeFaisUnServeurWeb/0.1
+181
+182""".encode()
+183
+184returnheader+content,code
+
+
+
+
Generates the proper answer, including the HTTP headers and content of the
+webpage, and the status code.
content_type: str
+The content type for the resource.
+
code: int
+The status code.
+
+
+
Returns
+
+
tuple
+ The reply for the request, including the data and status code.
+ - data: str
+ The data (header + content) to reply on the socket.
+ - code: int
+ The status code for the reply.
1######################################################################
+ 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
+ 3#
+ 4# This program and the accompanying materials are made
+ 5# available under the terms of the Eclipse Public License 2.0
+ 6# which is available at https://www.eclipse.org/legal/epl-2.0/
+ 7#
+ 8# SPDX-License-Identifier: EPL-2.0
+ 9######################################################################
+10
+11"""
+12Script to test the myserver module.
+13"""
+14
+15importpytest
+16importsys
+17fromos.pathimportdirname,join
+18
+19frommyserver.fileimportresolve_location,resolve_path,get_resource
+20
+21
+22def_find_root():
+23"""
+24 A utility to return the path of the script for test resources.
+25 """
+26tests_dir=dirname(__file__)
+27returntests_dir
+28
+29
+30deftest_resolve_path():
+31"""
+32 Tests the resolve_path() function with some normal use cases.
+33 """
+34tests_path=_find_root()
+35res_path=join(tests_path,'resources','www')
+36
+37# Normal resource.
+38full_path=resolve_path(res='/index.html',root=res_path)
+39print(full_path)
+40assertfull_path.endswith('resources/www/index.html')
+41
+42# Let's try with a folder in resource, with an image.
+43full_path=resolve_path(res='/images/chaton.jpg',root=res_path)
+44print(full_path)
+45assertfull_path.endswith('resources/www/images/chaton.jpg')
+46
+47
+48deftest_resolve_path_weirdos():
+49"""
+50 Tests the resolve_path() function with some abnormal use cases.
+51 """
+52tests_path=_find_root()
+53res_path=join(tests_path,'resources','www')
+54
+55# //index.html should be the same as /index.html.
+56full_path=resolve_path(res='//index.html',root=res_path)
+57print(full_path)
+58assertfull_path.endswith('resources/www/index.html')
+59
+60# / should return /index.html path
+61full_path=resolve_path(res='/',root=res_path)
+62print(full_path)
+63assertfull_path.endswith('resources/www/index.html')
+64
+65# Non-existing resources should return the empty string ''.
+66full_path=resolve_path(res='/index.dontexist',root=res_path)
+67print(full_path)
+68assertfull_path==''
+69
+70
+71deftest_get_resource():
+72"""
+73 Tests the get_resource() function by checking the content of the returned file.
+74 """
+75tests_path=_find_root()
+76res_path=join(tests_path,'resources','www','index.html')
+77out=get_resource(res_path=res_path)
+78print(out)
+79assertout[0]==b' <!DOCTYPE html>\n<html>\n<body>\n\n<h1>My First Heading</h1>\n<p>My first paragraph. See https://www.w3schools.com/html/html_basic.asp</p>\n\n</body>\n</html> '
+80
+81deftest_get_resource_image():
+82"""
+83 Tests the get_resource() function by checking the content of an image.
+84
+85 For an image, check we get the magic numbers for a JPEG.
+86 See https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5 for a complete list.
+87 """
+88tests_path=_find_root()
+89res_path=join(tests_path,'resources','www','images','chaton.jpg')
+90out=get_resource(res_path=res_path)
+91print(out[0][:4])
+92assertout[0][:4]==b'\xff\xd8\xff\xe0'
+93
+94
+
+
+
+
+
+
+
+
+ def
+ test_resolve_path():
+
+
+
+
+
+
31deftest_resolve_path():
+32"""
+33 Tests the resolve_path() function with some normal use cases.
+34 """
+35tests_path=_find_root()
+36res_path=join(tests_path,'resources','www')
+37
+38# Normal resource.
+39full_path=resolve_path(res='/index.html',root=res_path)
+40print(full_path)
+41assertfull_path.endswith('resources/www/index.html')
+42
+43# Let's try with a folder in resource, with an image.
+44full_path=resolve_path(res='/images/chaton.jpg',root=res_path)
+45print(full_path)
+46assertfull_path.endswith('resources/www/images/chaton.jpg')
+
+
+
+
Tests the resolve_path() function with some normal use cases.
+
+
+
+
+
+
+
+
+ def
+ test_resolve_path_weirdos():
+
+
+
+
+
+
49deftest_resolve_path_weirdos():
+50"""
+51 Tests the resolve_path() function with some abnormal use cases.
+52 """
+53tests_path=_find_root()
+54res_path=join(tests_path,'resources','www')
+55
+56# //index.html should be the same as /index.html.
+57full_path=resolve_path(res='//index.html',root=res_path)
+58print(full_path)
+59assertfull_path.endswith('resources/www/index.html')
+60
+61# / should return /index.html path
+62full_path=resolve_path(res='/',root=res_path)
+63print(full_path)
+64assertfull_path.endswith('resources/www/index.html')
+65
+66# Non-existing resources should return the empty string ''.
+67full_path=resolve_path(res='/index.dontexist',root=res_path)
+68print(full_path)
+69assertfull_path==''
+
+
+
+
Tests the resolve_path() function with some abnormal use cases.
+
+
+
+
+
+
+
+
+ def
+ test_get_resource():
+
+
+
+
+
+
72deftest_get_resource():
+73"""
+74 Tests the get_resource() function by checking the content of the returned file.
+75 """
+76tests_path=_find_root()
+77res_path=join(tests_path,'resources','www','index.html')
+78out=get_resource(res_path=res_path)
+79print(out)
+80assertout[0]==b' <!DOCTYPE html>\n<html>\n<body>\n\n<h1>My First Heading</h1>\n<p>My first paragraph. See https://www.w3schools.com/html/html_basic.asp</p>\n\n</body>\n</html> '
+
+
+
+
Tests the get_resource() function by checking the content of the returned file.
+
+
+
+
+
+
+
+
+ def
+ test_get_resource_image():
+
+
+
+
+
+
82deftest_get_resource_image():
+83"""
+84 Tests the get_resource() function by checking the content of an image.
+85
+86 For an image, check we get the magic numbers for a JPEG.
+87 See https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5 for a complete list.
+88 """
+89tests_path=_find_root()
+90res_path=join(tests_path,'resources','www','images','chaton.jpg')
+91out=get_resource(res_path=res_path)
+92print(out[0][:4])
+93assertout[0][:4]==b'\xff\xd8\xff\xe0'
+
+
+
+
Tests the get_resource() function by checking the content of an image.
1######################################################################
- 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
- 3#
- 4# This program and the accompanying materials are made
- 5# available under the terms of the Eclipse Public License 2.0
- 6# which is available at https://www.eclipse.org/legal/epl-2.0/
- 7#
- 8# SPDX-License-Identifier: EPL-2.0
- 9######################################################################
-10
-11"""
-12Script to test the myserver module.
-13"""
-14
-15importpytest
-16importio
-17fromcontextlibimportredirect_stdout
-18
-19frommyserver.logimportlog,log_address,log_request,log_reply
-20
-21
-22deftest_log():
-23"""
-24 Tests the log() function by capturing stdout.
-25 """
-26f=io.StringIO()
-27withredirect_stdout(f):
-28log('test message')
-29out=f.getvalue()
-30print(out)
-31assertout.endswith(' - test message\n')
-32
-33
-34deftest_log_address():
-35"""
-36 Tests the log_address() function by capturing stdout.
-37 """
-38f=io.StringIO()
-39withredirect_stdout(f):
-40log_address(('0.0.0.0',12345),'test message')
-41out=f.getvalue()
-42print(out)
-43assertout.endswith(' - 0.0.0.0:12345 - test message\n')
+
1######################################################################
+ 2# Copyright (c) Adrien Luxey-Bitri, Boris Baldassari
+ 3#
+ 4# This program and the accompanying materials are made
+ 5# available under the terms of the Eclipse Public License 2.0
+ 6# which is available at https://www.eclipse.org/legal/epl-2.0/
+ 7#
+ 8# SPDX-License-Identifier: EPL-2.0
+ 9######################################################################
+ 10
+ 11"""
+ 12Script to test the myserver module.
+ 13"""
+ 14
+ 15importpytest
+ 16importio
+ 17fromcontextlibimportredirect_stdout
+ 18
+ 19frommyserver.logimportlog,log_address,log_request,log_reply
+ 20
+ 21
+ 22deftest_log():
+ 23"""
+ 24 Tests the log() function by capturing stdout.
+ 25 """
+ 26f=io.StringIO()
+ 27withredirect_stdout(f):
+ 28log('test message')
+ 29out=f.getvalue()
+ 30print(out)
+ 31assertout.endswith(' - test message\n')
+ 32
+ 33
+ 34deftest_log_address():
+ 35"""
+ 36 Tests the log_address() function by capturing stdout.
+ 37 """
+ 38f=io.StringIO()
+ 39withredirect_stdout(f):
+ 40log_address(('0.0.0.0',12345),'test message')
+ 41out=f.getvalue()
+ 42print(out)
+ 43assertout.endswith(' - 0.0.0.0:12345 - test message\n')
+ 44
+ 45
+ 46deftest_log_request():
+ 47"""
+ 48 Tests the log_request() function by capturing stdout.
+ 49 """
+ 50req={
+ 51'head':{
+ 52'verb':'GET',
+ 53'resource':'/index.html'
+ 54}
+ 55}
+ 56f=io.StringIO()
+ 57withredirect_stdout(f):
+ 58log_request(('0.0.0.0',12345),req)
+ 59out=f.getvalue()
+ 60print(out)
+ 61assertout.endswith(' - 0.0.0.0:12345 - GET /index.html\n')
+ 62
+ 63deftest_log_request_useragent():
+ 64"""
+ 65 Tests the log_request() function by capturing stdout.
+ 66 """
+ 67req={
+ 68'head':{
+ 69'verb':'GET',
+ 70'resource':'/index.html'
+ 71},
+ 72'params':{
+ 73'User-Agent':'MyTest UserAgent v1.23'
+ 74}
+ 75}
+ 76f=io.StringIO()
+ 77withredirect_stdout(f):
+ 78log_request(('0.0.0.0',12345),req)
+ 79out=f.getvalue()
+ 80print(out)
+ 81assertout.endswith(' - 0.0.0.0:12345 - GET /index.html - MyTest UserAgent v1.23\n')
+ 82
+ 83deftest_log_request_failed_assert():
+ 84"""
+ 85 Tests if the log_request() function properly fails when wrong
+ 86 parameters are provided.
+ 87 """
+ 88req={
+ 89'head':{
+ 90'verb':'GET',
+ 91}
+ 92}
+ 93withpytest.raises(AssertionError)ase_info:
+ 94log_request(('0.0.0.0',12345),req)
+ 95
+ 96req={
+ 97'whatever':{
+ 98'verb':'GET',
+ 99'resource':'/index.html'
+100}
+101}
+102withpytest.raises(AssertionError)ase_info:
+103log_request(('0.0.0.0',12345),req)
+104
+105deftest_log_reply():
+106"""
+107 Tests the log_reply() function by checking its output.
+108 """
+109req={
+110'head':{
+111'verb':'GET',
+112'resource':'/index.html'
+113}
+114}
+115f=io.StringIO()
+116withredirect_stdout(f):
+117log_reply(('0.0.0.0',12345),req,200)
+118out=f.getvalue()
+119print(out)
+120assertout.endswith(' - 0.0.0.0:12345 - GET /index.html - 200\n')
+121
+122# Check a different status code.
+123withredirect_stdout(f):
+124log_reply(('0.0.0.0',12345),req,403)
+125out=f.getvalue()
+126print(out)
+127assertout.endswith(' - 0.0.0.0:12345 - GET /index.html - 403\n')
+128
+129deftest_log_reply_useragent():
+130"""
+131 Tests the log_reply() function by checking its output.
+132 """
+133req={
+134'head':{
+135'verb':'GET',
+136'resource':'/index.html'
+137},
+138'params':{
+139'User-Agent':'MyTest UserAgent v1.23'
+140}
+141}
+142f=io.StringIO()
+143withredirect_stdout(f):
+144log_reply(('0.0.0.0',12345),req,200)
+145out=f.getvalue()
+146print(out)
+147assertout.endswith(' - 0.0.0.0:12345 - GET /index.html - 200 - MyTest UserAgent v1.23\n')
+148
+149withredirect_stdout(f):
+150log_reply(('127.0.0.1',12345),req,502)
+151out=f.getvalue()
+152print(out)
+153assertout.endswith(' - 127.0.0.1:12345 - GET /index.html - 502 - MyTest UserAgent v1.23\n')
@@ -163,6 +288,206 @@
+
+
+
+
+
+ def
+ test_log_request():
+
+
+
+
+
+
47deftest_log_request():
+48"""
+49 Tests the log_request() function by capturing stdout.
+50 """
+51req={
+52'head':{
+53'verb':'GET',
+54'resource':'/index.html'
+55}
+56}
+57f=io.StringIO()
+58withredirect_stdout(f):
+59log_request(('0.0.0.0',12345),req)
+60out=f.getvalue()
+61print(out)
+62assertout.endswith(' - 0.0.0.0:12345 - GET /index.html\n')
+
+
+
+
Tests the log_request() function by capturing stdout.
+
+
+
+
+
+
+
+
+ def
+ test_log_request_useragent():
+
+
+
+
+
+
64deftest_log_request_useragent():
+65"""
+66 Tests the log_request() function by capturing stdout.
+67 """
+68req={
+69'head':{
+70'verb':'GET',
+71'resource':'/index.html'
+72},
+73'params':{
+74'User-Agent':'MyTest UserAgent v1.23'
+75}
+76}
+77f=io.StringIO()
+78withredirect_stdout(f):
+79log_request(('0.0.0.0',12345),req)
+80out=f.getvalue()
+81print(out)
+82assertout.endswith(' - 0.0.0.0:12345 - GET /index.html - MyTest UserAgent v1.23\n')
+
+
+
+
Tests the log_request() function by capturing stdout.