diff --git a/scripts/03_server/docs/index.html b/scripts/03_server/docs/index.html index 737948d..f899090 100644 --- a/scripts/03_server/docs/index.html +++ b/scripts/03_server/docs/index.html @@ -19,18 +19,20 @@

Available Modules

diff --git a/scripts/03_server/docs/search.js b/scripts/03_server/docs/search.js index 1d8fec8..96e3094 100644 --- a/scripts/03_server/docs/search.js +++ b/scripts/03_server/docs/search.js @@ -1,6 +1,6 @@ window.pdocSearch = (function(){ /** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o

\n"}, {"fullname": "src.myserver", "modulename": "src.myserver", "kind": "module", "doc": "

\n"}, {"fullname": "src.myserver.cli", "modulename": "src.myserver.cli", "kind": "module", "doc": "

A module for learning network programming in Python.

\n\n

This functions manage the command line interface to control the server.

\n"}, {"fullname": "src.myserver.cli.parse_args", "modulename": "src.myserver.cli", "qualname": "parse_args", "kind": "function", "doc": "

Parses arguments from command line.

\n\n

Parameters

\n\n
    \n
  • argv: list[str]\nThe list of arguments to parse.
  • \n
\n\n

Returns

\n\n

argparse.Namespace\n The list of parameters and their values.

\n", "signature": "(argv: list[str]) -> argparse.Namespace:", "funcdef": "def"}, {"fullname": "src.myserver.cli.main", "modulename": "src.myserver.cli", "qualname": "main", "kind": "function", "doc": "

\n", "signature": "():", "funcdef": "def"}, {"fullname": "src.myserver.date", "modulename": "src.myserver.date", "kind": "module", "doc": "

\n"}, {"fullname": "src.myserver.date.now_rfc2616", "modulename": "src.myserver.date", "qualname": "now_rfc2616", "kind": "function", "doc": "

\n", "signature": "():", "funcdef": "def"}, {"fullname": "src.myserver.file", "modulename": "src.myserver.file", "kind": "module", "doc": "

\n"}, {"fullname": "src.myserver.file.get_resource", "modulename": "src.myserver.file", "qualname": "get_resource", "kind": "function", "doc": "

Returns the resource requested by a GET request.

\n\n

Parameters

\n\n
    \n
  • res: str\nRequested resource string.
  • \n
  • root: str\nRoot directory of the server.
  • \n
\n\n

Returns

\n\n

int \n The response HTTP code.\nbytes\n The resource content.

\n", "signature": "(res: str, root: str):", "funcdef": "def"}, {"fullname": "src.myserver.file.resolve_location", "modulename": "src.myserver.file", "qualname": "resolve_location", "kind": "function", "doc": "

\n", "signature": "(res: str, root: str):", "funcdef": "def"}, {"fullname": "src.myserver.http_request", "modulename": "src.myserver.http_request", "kind": "module", "doc": "

A module for learning network programming in Python.

\n\n

This functions handle the http messages.

\n"}, {"fullname": "src.myserver.http_request.parse_request", "modulename": "src.myserver.http_request", "qualname": "parse_request", "kind": "function", "doc": "

Parses a full HTTP request bytes buffer into a dict.

\n\n

The parsed request dict contains two keys:

\n\n
    \n
  • head: dict[str, str]\nInformation on the HTTP request header (i.e. the first request line);\noutput of parse_request_head.
  • \n
  • params: dict[str, str]\nList of the HTTP parameters (i.e. the following lines); \noutput of parse_request_params.
  • \n
\n\n

Parameters

\n\n
    \n
  • buf: bytes \nThe HTTP request buffer.
  • \n
\n\n

Returns

\n\n

dict[str, dict]\n The parsed content of the HTTP request.

\n\n

Raises

\n\n

ValueError\n The request is not valid HTTP.

\n", "signature": "(buf: bytes) -> dict[str, dict]:", "funcdef": "def"}, {"fullname": "src.myserver.http_request.parse_request_head", "modulename": "src.myserver.http_request", "qualname": "parse_request_head", "kind": "function", "doc": "

Parses a HTTP request header string (its first line) into a dict.

\n\n

The parsed request dict contains two keys:

\n\n
    \n
  • verb: str\nThe _uppercase_ verb of the request, i.e. the first word of the line;\nfor example: \"GET\".
  • \n
  • resource: str\nThe requested resource, i.e. the second \"word\" of the line;\nfor example: \"/index.html\".
  • \n
\n\n

Parameters

\n\n
    \n
  • line: str\nThe HTTP request header (the first line of a full HTTP request).
  • \n
\n\n

Returns

\n\n

dict[str, str]\n The parsed content of the HTTP request header.

\n\n

Raises

\n\n

ValueError\n The request header is not valid HTTP.

\n", "signature": "(line: str) -> dict[str, str]:", "funcdef": "def"}, {"fullname": "src.myserver.http_request.parse_request_params", "modulename": "src.myserver.http_request", "qualname": "parse_request_params", "kind": "function", "doc": "

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.

\n", "signature": "(lines: list[str]) -> dict[str, str]:", "funcdef": "def"}, {"fullname": "src.myserver.log", "modulename": "src.myserver.log", "kind": "module", "doc": "

A module for learning network programming in Python.

\n\n

This functions handle the http messages.

\n"}, {"fullname": "src.myserver.log.log", "modulename": "src.myserver.log", "qualname": "log", "kind": "function", "doc": "

Logs a message to stdout, with a timestamp.

\n\n

Output is: timestamp - message.

\n\n

Parameters

\n\n
    \n
  • msg : str \nThe message string to print.
  • \n
\n", "signature": "(msg: str):", "funcdef": "def"}, {"fullname": "src.myserver.log.log_address", "modulename": "src.myserver.log", "qualname": "log_address", "kind": "function", "doc": "

Logs a message to stdout, with a timestamp and an address (host:port).

\n\n

Output is: timestamp - host:port - message.

\n\n

Parameters

\n\n
    \n
  • addr: tuple[str, int]\nThe address to print, as a tuple (host, port)
  • \n
  • msg: str\nThe message string to print.
  • \n
\n", "signature": "(addr: tuple[str, int], msg: str):", "funcdef": "def"}, {"fullname": "src.myserver.log.log_request", "modulename": "src.myserver.log", "qualname": "log_request", "kind": "function", "doc": "

Logs a request message to stdout, with a timestamp and an address (host:port).

\n\n

Output is: timestamp - host:port - message.

\n\n

Parameters

\n\n
    \n
  • addr: tuple[str, int]\nThe address to print, as a tuple (host, port)
  • \n
  • req: dict[str, dict]\nThe request to print.
  • \n
\n", "signature": "(addr: tuple[str, int], req: dict[str, dict]):", "funcdef": "def"}, {"fullname": "src.myserver.log.log_reply", "modulename": "src.myserver.log", "qualname": "log_reply", "kind": "function", "doc": "

Logs a reply message to stdout, with a timestamp and an address (host:port).

\n\n

Output is: timestamp - host:port - message.

\n\n

Parameters

\n\n
    \n
  • addr: tuple[str, int]\nThe address to print, as a tuple (host, port)
  • \n
  • req: dict[str, dict]\nThe request to print.
  • \n
  • code: int\nThe replied code to print.
  • \n
\n", "signature": "(addr: tuple[str, int], req: dict[str, dict], code: int):", "funcdef": "def"}, {"fullname": "src.myserver.server", "modulename": "src.myserver.server", "kind": "module", "doc": "

A module for learning network programming in Python.

\n\n

This functions manage the socket connections and multi-threading of clients.

\n"}, {"fullname": "src.myserver.server.serve", "modulename": "src.myserver.server", "qualname": "serve", "kind": "function", "doc": "

Serves http request connections for clients.

\n\n

This function creates the network socket, listens, and calls\n~myserver.handle_client()() when a request comes in.

\n", "signature": "(port: int, root: str):", "funcdef": "def"}, {"fullname": "src.myserver.server.handle_client", "modulename": "src.myserver.server", "qualname": "handle_client", "kind": "function", "doc": "

\n", "signature": "(c: socket.socket, addr: tuple[str, int], root: str):", "funcdef": "def"}, {"fullname": "src.myserver.server.reply", "modulename": "src.myserver.server", "qualname": "reply", "kind": "function", "doc": "

\n", "signature": "(\tc: socket.socket,\taddr: tuple[str, int],\treq: dict,\tcode: int,\tcontent: bytes):", "funcdef": "def"}, {"fullname": "tests", "modulename": "tests", "kind": "module", "doc": "

\n"}, {"fullname": "tests.test_http_request", "modulename": "tests.test_http_request", "kind": "module", "doc": "

Script to test the server.py module.

\n"}, {"fullname": "tests.test_http_request.testdata_request_head", "modulename": "tests.test_http_request", "qualname": "testdata_request_head", "kind": "variable", "doc": "

\n", "default_value": "[('GET / HTTP/1.1', {'verb': 'GET', 'resource': '/'}), ('options /assets/style.css HTTP/1.1', {'verb': 'OPTIONS', 'resource': '/assets/style.css'})]"}, {"fullname": "tests.test_http_request.test_parse_request_head", "modulename": "tests.test_http_request", "qualname": "test_parse_request_head", "kind": "function", "doc": "

\n", "signature": "(head: str, expected: dict):", "funcdef": "def"}, {"fullname": "tests.test_http_request.test_parse_request_head_invalid", "modulename": "tests.test_http_request", "qualname": "test_parse_request_head_invalid", "kind": "function", "doc": "

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_http_request.testdata_request_params", "modulename": "tests.test_http_request", "qualname": "testdata_request_params", "kind": "variable", "doc": "

\n", "default_value": "[([], {}), (['Host: example.org', 'User-Agent: curl/7.81.0', 'Accept: */*'], {'Host': 'example.org', 'User-Agent': 'curl/7.81.0', 'Accept': '*/*'}), ([' Host: example.org ', 'Accept-Encoding: gzip, deflate, br'], {'Host': 'example.org', 'Accept-Encoding': 'gzip, deflate, br'})]"}, {"fullname": "tests.test_http_request.test_parse_request_params", "modulename": "tests.test_http_request", "qualname": "test_parse_request_params", "kind": "function", "doc": "

\n", "signature": "(params: list[str], expected: dict):", "funcdef": "def"}, {"fullname": "tests.test_http_request.testdata_request_params_invalid", "modulename": "tests.test_http_request", "qualname": "testdata_request_params_invalid", "kind": "variable", "doc": "

\n", "default_value": "[['Host example.org'], [': example.org'], ['Truc: ']]"}, {"fullname": "tests.test_http_request.test_parse_request_params_invalid", "modulename": "tests.test_http_request", "qualname": "test_parse_request_params_invalid", "kind": "function", "doc": "

\n", "signature": "(params: list[str]):", "funcdef": "def"}, {"fullname": "tests.test_http_request.testdata_request", "modulename": "tests.test_http_request", "qualname": "testdata_request", "kind": "variable", "doc": "

\n", "default_value": "[(b'GET /index.html HTTP/1.1\\nHost: example.org\\nUser-Agent: curl/7.81.0\\nAccept: */*\\n\\n', {'head': {'verb': 'GET', 'resource': '/index.html'}, 'params': {'Host': 'example.org', 'User-Agent': 'curl/7.81.0', 'Accept': '*/*'}}), (b'\\n\\nOPTIONS /assets/style.css HTTP/1.1\\n\\n', {'head': {'verb': 'OPTIONS', 'resource': '/assets/style.css'}, 'params': {}})]"}, {"fullname": "tests.test_http_request.test_parse_request", "modulename": "tests.test_http_request", "qualname": "test_parse_request", "kind": "function", "doc": "

\n", "signature": "(buf: bytes, expected: dict):", "funcdef": "def"}, {"fullname": "tests.test_http_request.testdata_request_invalid", "modulename": "tests.test_http_request", "qualname": "testdata_request_invalid", "kind": "variable", "doc": "

\n", "default_value": "[b'Host: example.org\\nUser-Agent: curl/7.81.0\\n\\n', b'\\nGET HTTP/1.1\\n\\n', b'GET / HTTP/1.1\\nHost example.org\\n\\nGET / HTTP/1.1\\nHost: example.org\\n\\nUser-Agent: curl/7.81.0\\n\\n']"}, {"fullname": "tests.test_http_request.test_parse_request_invalid", "modulename": "tests.test_http_request", "qualname": "test_parse_request_invalid", "kind": "function", "doc": "

\n", "signature": "(buf: bytes):", "funcdef": "def"}, {"fullname": "tests.test_log", "modulename": "tests.test_log", "kind": "module", "doc": "

Script to test the myserver module.

\n"}, {"fullname": "tests.test_log.test_log", "modulename": "tests.test_log", "qualname": "test_log", "kind": "function", "doc": "

Tests the log() function by capturing stdout.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_log.test_log_address", "modulename": "tests.test_log", "qualname": "test_log_address", "kind": "function", "doc": "

Tests the log_address() function by capturing stdout.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_server", "modulename": "tests.test_server", "kind": "module", "doc": "

Script to test the server.py module.

\n"}, {"fullname": "tests.test_server.HTTP_PORT", "modulename": "tests.test_server", "qualname": "HTTP_PORT", "kind": "variable", "doc": "

The port to use during the tests, both for the server and client.

\n", "default_value": "12345"}, {"fullname": "tests.test_server.start_server", "modulename": "tests.test_server", "qualname": "start_server", "kind": "function", "doc": "

Automatically starts and stops the server when running the tests.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_server.test_serve", "modulename": "tests.test_server", "qualname": "test_serve", "kind": "function", "doc": "

Tests the serve() function by using the requests module.

\n\n

Uses the start_server fixture to start and stop the serverduring the tests.

\n", "signature": "(start_server):", "funcdef": "def"}]; + /** pdoc search index */const docs = [{"fullname": "tests", "modulename": "tests", "kind": "module", "doc": "

\n"}, {"fullname": "tests.test_file", "modulename": "tests.test_file", "kind": "module", "doc": "

Script to test the myserver module.

\n"}, {"fullname": "tests.test_file.test_resolve_path", "modulename": "tests.test_file", "qualname": "test_resolve_path", "kind": "function", "doc": "

Tests the resolve_path() function with some normal use cases.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_file.test_resolve_path_weirdos", "modulename": "tests.test_file", "qualname": "test_resolve_path_weirdos", "kind": "function", "doc": "

Tests the resolve_path() function with some abnormal use cases.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_file.test_get_resource", "modulename": "tests.test_file", "qualname": "test_get_resource", "kind": "function", "doc": "

Tests the get_resource() function by checking the content of the returned file.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_file.test_get_resource_image", "modulename": "tests.test_file", "qualname": "test_get_resource_image", "kind": "function", "doc": "

Tests the get_resource() function by checking the content of an image.

\n\n

For an image, check we get the magic numbers for a JPEG.\nSee https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5 for a complete list.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_http_request", "modulename": "tests.test_http_request", "kind": "module", "doc": "

Script to test the server.py module.

\n"}, {"fullname": "tests.test_http_request.testdata_request_head", "modulename": "tests.test_http_request", "qualname": "testdata_request_head", "kind": "variable", "doc": "

\n", "default_value": "[('GET / HTTP/1.1', {'verb': 'GET', 'resource': '/'}), ('options /assets/style.css HTTP/1.1', {'verb': 'OPTIONS', 'resource': '/assets/style.css'})]"}, {"fullname": "tests.test_http_request.test_parse_request_head", "modulename": "tests.test_http_request", "qualname": "test_parse_request_head", "kind": "function", "doc": "

\n", "signature": "(head: str, expected: dict):", "funcdef": "def"}, {"fullname": "tests.test_http_request.test_parse_request_head_invalid", "modulename": "tests.test_http_request", "qualname": "test_parse_request_head_invalid", "kind": "function", "doc": "

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_http_request.testdata_request_params", "modulename": "tests.test_http_request", "qualname": "testdata_request_params", "kind": "variable", "doc": "

\n", "default_value": "[([], {}), (['Host: example.org', 'User-Agent: curl/7.81.0', 'Accept: */*'], {'Host': 'example.org', 'User-Agent': 'curl/7.81.0', 'Accept': '*/*'}), ([' Host: example.org ', 'Accept-Encoding: gzip, deflate, br'], {'Host': 'example.org', 'Accept-Encoding': 'gzip, deflate, br'})]"}, {"fullname": "tests.test_http_request.test_parse_request_params", "modulename": "tests.test_http_request", "qualname": "test_parse_request_params", "kind": "function", "doc": "

\n", "signature": "(params: list[str], expected: dict):", "funcdef": "def"}, {"fullname": "tests.test_http_request.testdata_request_params_invalid", "modulename": "tests.test_http_request", "qualname": "testdata_request_params_invalid", "kind": "variable", "doc": "

\n", "default_value": "[['Host example.org'], [': example.org'], ['Truc: ']]"}, {"fullname": "tests.test_http_request.test_parse_request_params_invalid", "modulename": "tests.test_http_request", "qualname": "test_parse_request_params_invalid", "kind": "function", "doc": "

\n", "signature": "(params: list[str]):", "funcdef": "def"}, {"fullname": "tests.test_http_request.testdata_request", "modulename": "tests.test_http_request", "qualname": "testdata_request", "kind": "variable", "doc": "

\n", "default_value": "[(b'GET /index.html HTTP/1.1\\nHost: example.org\\nUser-Agent: curl/7.81.0\\nAccept: */*\\n\\n', {'head': {'verb': 'GET', 'resource': '/index.html'}, 'params': {'Host': 'example.org', 'User-Agent': 'curl/7.81.0', 'Accept': '*/*'}}), (b'\\n\\nOPTIONS /assets/style.css HTTP/1.1\\n\\n', {'head': {'verb': 'OPTIONS', 'resource': '/assets/style.css'}, 'params': {}})]"}, {"fullname": "tests.test_http_request.test_parse_request", "modulename": "tests.test_http_request", "qualname": "test_parse_request", "kind": "function", "doc": "

\n", "signature": "(buf: bytes, expected: dict):", "funcdef": "def"}, {"fullname": "tests.test_http_request.testdata_request_invalid", "modulename": "tests.test_http_request", "qualname": "testdata_request_invalid", "kind": "variable", "doc": "

\n", "default_value": "[b'Host: example.org\\nUser-Agent: curl/7.81.0\\n\\n', b'\\nGET HTTP/1.1\\n\\n', b'GET / HTTP/1.1\\nHost example.org\\n\\nGET / HTTP/1.1\\nHost: example.org\\n\\nUser-Agent: curl/7.81.0\\n\\n']"}, {"fullname": "tests.test_http_request.test_parse_request_invalid", "modulename": "tests.test_http_request", "qualname": "test_parse_request_invalid", "kind": "function", "doc": "

\n", "signature": "(buf: bytes):", "funcdef": "def"}, {"fullname": "tests.test_log", "modulename": "tests.test_log", "kind": "module", "doc": "

Script to test the myserver module.

\n"}, {"fullname": "tests.test_log.test_log", "modulename": "tests.test_log", "qualname": "test_log", "kind": "function", "doc": "

Tests the log() function by capturing stdout.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_log.test_log_address", "modulename": "tests.test_log", "qualname": "test_log_address", "kind": "function", "doc": "

Tests the log_address() function by capturing stdout.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_log.test_log_request", "modulename": "tests.test_log", "qualname": "test_log_request", "kind": "function", "doc": "

Tests the log_request() function by capturing stdout.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_log.test_log_request_useragent", "modulename": "tests.test_log", "qualname": "test_log_request_useragent", "kind": "function", "doc": "

Tests the log_request() function by capturing stdout.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_log.test_log_request_failed_assert", "modulename": "tests.test_log", "qualname": "test_log_request_failed_assert", "kind": "function", "doc": "

Tests if the log_request() function properly fails when wrong\nparameters are provided.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_log.test_log_reply", "modulename": "tests.test_log", "qualname": "test_log_reply", "kind": "function", "doc": "

Tests the log_reply() function by checking its output.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_log.test_log_reply_useragent", "modulename": "tests.test_log", "qualname": "test_log_reply_useragent", "kind": "function", "doc": "

Tests the log_reply() function by checking its output.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_server", "modulename": "tests.test_server", "kind": "module", "doc": "

Script to test the server.py module.

\n"}, {"fullname": "tests.test_server.HTTP_PORT", "modulename": "tests.test_server", "qualname": "HTTP_PORT", "kind": "variable", "doc": "

The port to use during the tests, both for the server and client.

\n", "default_value": "12345"}, {"fullname": "tests.test_server.start_server", "modulename": "tests.test_server", "qualname": "start_server", "kind": "function", "doc": "

Automatically starts and stops the server when running the tests.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "tests.test_server.test_serve", "modulename": "tests.test_server", "qualname": "test_serve", "kind": "function", "doc": "

Tests the serve() function by using the requests module.

\n\n

Uses the start_server fixture to start and stop the serverduring the tests.

\n", "signature": "():", "funcdef": "def"}, {"fullname": "src", "modulename": "src", "kind": "module", "doc": "

\n"}, {"fullname": "src.myserver", "modulename": "src.myserver", "kind": "module", "doc": "

A package for learning network programming in Python.

\n\n

This module (file) defines the directory as a Python module.

\n"}, {"fullname": "src.myserver.cli", "modulename": "src.myserver.cli", "kind": "module", "doc": "

A package for learning network programming in Python.

\n\n

This module (file) manages the command line interface to control the server.

\n"}, {"fullname": "src.myserver.cli.parse_args", "modulename": "src.myserver.cli", "qualname": "parse_args", "kind": "function", "doc": "

Parses arguments from command line.

\n\n

Parameters

\n\n
    \n
  • argv: list[str]\nThe list of arguments to parse.
  • \n
\n\n

Returns

\n\n

argparse.Namespace\n The list of parameters and their values.

\n", "signature": "(argv: list[str]) -> argparse.Namespace:", "funcdef": "def"}, {"fullname": "src.myserver.cli.main", "modulename": "src.myserver.cli", "qualname": "main", "kind": "function", "doc": "

\n", "signature": "():", "funcdef": "def"}, {"fullname": "src.myserver.date", "modulename": "src.myserver.date", "kind": "module", "doc": "

A package for learning network programming in Python.

\n\n

This module (file) manages date related-utilities.

\n"}, {"fullname": "src.myserver.date.now_rfc2616", "modulename": "src.myserver.date", "qualname": "now_rfc2616", "kind": "function", "doc": "

\n", "signature": "():", "funcdef": "def"}, {"fullname": "src.myserver.file", "modulename": "src.myserver.file", "kind": "module", "doc": "

A package for learning network programming in Python.

\n\n

This module (file) manages operations relative to the file system.

\n"}, {"fullname": "src.myserver.file.resolve_location", "modulename": "src.myserver.file", "qualname": "resolve_location", "kind": "function", "doc": "

Returns the path of a resource relative to the root and its extension.

\n\n

Returns (\"\", \"\") if the concatenated path does not exist.

\n\n

\"index.html\" is appended to directory paths.

\n\n

Parameters

\n\n

res: str \n The queried resource path.\nroot: str \n The root directory where to look into for res.

\n\n

Returns

\n\n

str\n The full disk path of the resource if it exists, or \"\".\nstr\n The extension of the resource if it exists, or \"\".

\n", "signature": "(res: str, root: str):", "funcdef": "def"}, {"fullname": "src.myserver.file.resolve_path", "modulename": "src.myserver.file", "qualname": "resolve_path", "kind": "function", "doc": "

Returns the full disk path of a resource relative to the root.

\n\n

Returns \"\" if the concatenated path does not exist.

\n\n

\"index.html\" is appended to directory paths.

\n\n

Parameters

\n\n

res: str \n The queried resource path.\nroot: str \n The root directory where to look into for res.

\n\n

Returns

\n\n

str\n The full disk path of the resource if it exists, or \"\".

\n", "signature": "(res: str, root: str):", "funcdef": "def"}, {"fullname": "src.myserver.file.get_resource", "modulename": "src.myserver.file", "qualname": "get_resource", "kind": "function", "doc": "

Returns a resource at res_path, its content type and an HTTP code.

\n\n

Parameters

\n\n
    \n
  • res_path: str\nRequested resource string.
  • \n
\n\n

Returns

\n\n

bytes\n The resource content if it exists (code == 200).\nint \n A HTTP status code.

\n", "signature": "(res_path: str):", "funcdef": "def"}, {"fullname": "src.myserver.http", "modulename": "src.myserver.http", "kind": "module", "doc": "

A package for learning network programming in Python.

\n\n

This module (file) provides information relative to the HTTP specification.

\n"}, {"fullname": "src.myserver.http.get_http_code", "modulename": "src.myserver.http", "qualname": "get_http_code", "kind": "function", "doc": "

Returns a dict corresponding to the HTTP status code.

\n\n

See also : https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

\n\n

Parameters

\n\n
    \n
  • code: int \nAn HTTP code.
  • \n
\n\n

Returns

\n\n

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.

\n", "signature": "(code: int):", "funcdef": "def"}, {"fullname": "src.myserver.http.file_extension_to_content_type", "modulename": "src.myserver.http", "qualname": "file_extension_to_content_type", "kind": "variable", "doc": "

\n", "default_value": "{'webm': 'video/webm', 'mp3': 'audio/mpeg', 'wasm': 'application/wasm', 'crx': 'application/x-chrome-extension', 'xhtml': 'application/xhtml+xml', 'xht': 'application/xhtml+xml', 'xhtm': 'application/xhtml+xml', 'flac': 'audio/flac', 'ogg': 'audio/ogg', 'oga': 'audio/ogg', 'opus': 'audio/ogg', 'wav': 'audio/wav', 'm4a': 'audio/x-m4a', 'avif': 'image/avif', 'gif': 'image/gif', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'png': 'image/png', 'apng': 'image/apng', 'svg': 'image/svg+xml', 'svgz': 'image/svg+xml', 'webp': 'image/webp', 'mht': 'multipart/related', 'mhtml': 'multipart/related', 'css': 'text/css', 'html': 'text/html', 'htm': 'text/html', 'shtml': 'text/html', 'shtm': 'text/html', 'js': 'text/javascript', 'mjs': 'text/javascript', 'xml': 'text/xml', 'mp4': 'video/mp4', 'm4v': 'video/mp4', 'ogv': 'video/ogg', 'ogm': 'video/ogg', 'csv': 'text/csv', 'ico': 'image/vnd.microsoft.icon'}"}, {"fullname": "src.myserver.http.get_http_content_type", "modulename": "src.myserver.http", "qualname": "get_http_content_type", "kind": "function", "doc": "

Returns the HTTP Content-Type corresponding to a file extension.

\n\n

Returns \"application/octet-stream\" when the extension is unknown.

\n\n

Parameters

\n\n
    \n
  • extension: str\nA file extension.
  • \n
\n\n

Returns

\n\n

str \n An HTTP Content-Type

\n", "signature": "(extension: str):", "funcdef": "def"}, {"fullname": "src.myserver.http_request", "modulename": "src.myserver.http_request", "kind": "module", "doc": "

A package for learning network programming in Python.

\n\n

This module (file) manages the parsing of HTTP requests.

\n"}, {"fullname": "src.myserver.http_request.parse_request", "modulename": "src.myserver.http_request", "qualname": "parse_request", "kind": "function", "doc": "

Parses a full HTTP request bytes buffer into a dict.

\n\n

The parsed request dict contains two keys:

\n\n
    \n
  • head: dict[str, str]\nInformation on the HTTP request header (i.e. the first request line);\noutput of parse_request_head.
  • \n
  • params: dict[str, str]\nList of the HTTP parameters (i.e. the following lines); \noutput of parse_request_params.
  • \n
\n\n

Parameters

\n\n
    \n
  • buf: bytes \nThe HTTP request buffer.
  • \n
\n\n

Returns

\n\n

dict[str, dict]\n The parsed content of the HTTP request.

\n\n

Raises

\n\n

ValueError\n The request is not valid HTTP.

\n", "signature": "(buf: bytes) -> dict[str, dict]:", "funcdef": "def"}, {"fullname": "src.myserver.http_request.parse_request_head", "modulename": "src.myserver.http_request", "qualname": "parse_request_head", "kind": "function", "doc": "

Parses a HTTP request header string (its first line) into a dict.

\n\n

The parsed request dict contains two keys:

\n\n
    \n
  • verb: str\nThe _uppercase_ verb of the request, i.e. the first word of the line;\nfor example: \"GET\".
  • \n
  • resource: str\nThe requested resource, i.e. the second \"word\" of the line;\nfor example: \"/index.html\".
  • \n
\n\n

Parameters

\n\n
    \n
  • line: str\nThe HTTP request header (the first line of a full HTTP request).
  • \n
\n\n

Returns

\n\n

dict[str, str]\n The parsed content of the HTTP request header.

\n\n

Raises

\n\n

ValueError\n The request header is not valid HTTP.

\n", "signature": "(line: str) -> dict[str, str]:", "funcdef": "def"}, {"fullname": "src.myserver.http_request.parse_request_params", "modulename": "src.myserver.http_request", "qualname": "parse_request_params", "kind": "function", "doc": "

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.

\n", "signature": "(lines: list[str]) -> dict[str, str]:", "funcdef": "def"}, {"fullname": "src.myserver.log", "modulename": "src.myserver.log", "kind": "module", "doc": "

A package for learning network programming in Python.

\n\n

This module (file) manages the HTTP logging messages.

\n"}, {"fullname": "src.myserver.log.log", "modulename": "src.myserver.log", "qualname": "log", "kind": "function", "doc": "

Logs a message to stdout, with a timestamp.

\n\n

Output is: timestamp - message.

\n\n

Parameters

\n\n
    \n
  • msg : str \nThe message string to print.
  • \n
\n", "signature": "(msg: str):", "funcdef": "def"}, {"fullname": "src.myserver.log.log_address", "modulename": "src.myserver.log", "qualname": "log_address", "kind": "function", "doc": "

Logs a message to stdout, with a timestamp and an address (host:port).

\n\n

Output is: timestamp - host:port - message.

\n\n

Parameters

\n\n
    \n
  • addr: tuple[str, int]\nThe address to print, as a tuple (host, port)
  • \n
  • msg: str\nThe message string to print.
  • \n
\n", "signature": "(addr: tuple[str, int], msg: str):", "funcdef": "def"}, {"fullname": "src.myserver.log.log_request", "modulename": "src.myserver.log", "qualname": "log_request", "kind": "function", "doc": "

Logs a request message to stdout, with a timestamp and an address (host:port).

\n\n

Output is: timestamp - host:port - message.

\n\n

Parameters

\n\n
    \n
  • addr: tuple[str, int]\nThe address to print, as a tuple (host, port)
  • \n
  • req: dict[str, dict]\nThe request to print.
  • \n
\n", "signature": "(addr: tuple[str, int], req: dict[str, dict]):", "funcdef": "def"}, {"fullname": "src.myserver.log.log_reply", "modulename": "src.myserver.log", "qualname": "log_reply", "kind": "function", "doc": "

Logs an HTTP reply to stdout, with timestamp, address (host:port), code, UA.

\n\n

Output is: timestamp - host:port - HTTP-verb HTTP-resource - code - message.

\n\n

Parameters

\n\n
    \n
  • addr: tuple[str, int]\nThe address to print, as a tuple (host, port)
  • \n
  • req: dict[str, dict]\nThe request to print.
  • \n
  • code: int\nThe replied code to print.
  • \n
\n", "signature": "(addr: tuple[str, int], req: dict[str, dict], code: int):", "funcdef": "def"}, {"fullname": "src.myserver.server", "modulename": "src.myserver.server", "kind": "module", "doc": "

A package for learning network programming in Python.

\n\n

This part manages the socket connections and multi-threading of clients.

\n"}, {"fullname": "src.myserver.server.serve", "modulename": "src.myserver.server", "qualname": "serve", "kind": "function", "doc": "

Serves http request connections for clients.

\n\n

This function creates the network socket, listens, and as soon as it receives\na request (connection), calls the ~myserver.handle_client()().

\n", "signature": "(port: int, root: str):", "funcdef": "def"}, {"fullname": "src.myserver.server.handle_client", "modulename": "src.myserver.server", "qualname": "handle_client", "kind": "function", "doc": "

Manages a single connection from a client.

\n\n

In details, we:

\n\n
    \n
  • read data from the socket provided,
  • \n
  • parse this data to build the request and headers,
  • \n
  • call the handle_request() function,\n[* optionally write something in the log,]
  • \n
  • close the connection.
  • \n
\n\n

Parameters

\n\n
    \n
  • c: socket.socket\nThe socket to communicate with the client.
  • \n
  • addr: tuple[str, int]\nThe IP address and port of the client, as returned by the accept command.
  • \n
  • root: str\nThe path to the local directory to serve.
  • \n
\n", "signature": "(c: socket.socket, addr: tuple[str, int], root: str):", "funcdef": "def"}, {"fullname": "src.myserver.server.prepare_resource", "modulename": "src.myserver.server", "qualname": "prepare_resource", "kind": "function", "doc": "

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.

\n", "signature": "(root: str, req: dict):", "funcdef": "def"}, {"fullname": "src.myserver.server.prepare_reply", "modulename": "src.myserver.server", "qualname": "prepare_reply", "kind": "function", "doc": "

Generates the proper answer, including the HTTP headers and content of the\nwebpage, and the status code.

\n\n

For more information about:

\n\n\n\n

Parameters

\n\n
    \n
  • content: bytes\nThe raw data for the resource.
  • \n
  • 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 @@
  • cli
  • date
  • file
  • +
  • http
  • http_request
  • log
  • server
  • @@ -51,12 +52,30 @@

    src.myserver

    - +

    A package for learning network programming in Python.

    + +

    This module (file) defines the directory as a Python module.

    +
    + -
    1
    +                        
     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"""
     
    diff --git a/scripts/03_server/docs/src/myserver/cli.html b/scripts/03_server/docs/src/myserver/cli.html index c64adfb..3025538 100644 --- a/scripts/03_server/docs/src/myserver/cli.html +++ b/scripts/03_server/docs/src/myserver/cli.html @@ -52,9 +52,9 @@

    src.myserver.cli

    -

    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###################################################################### 10 11""" -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""" 16 17import argparse 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 @@

    src.myserver.date

    - +

    A package for learning network programming in Python.

    + +

    This module (file) manages date related-utilities.

    +
    + -
    1from time import gmtime, strftime
    -2
    -3_RFC2616_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
    -4
    -5def now_rfc2616():
    -6    return strftime(_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 date related-utilities.
    +15"""
    +16
    +17from time import gmtime, strftime
    +18
    +19_RFC2616_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
    +20
    +21def now_rfc2616():
    +22    return strftime(_RFC2616_DATE_FORMAT, gmtime())
     
    @@ -75,8 +95,8 @@
    -
    6def now_rfc2616():
    -7    return strftime(_RFC2616_DATE_FORMAT, gmtime())
    +            
    22def now_rfc2616():
    +23    return strftime(_RFC2616_DATE_FORMAT, gmtime())
     
    diff --git a/scripts/03_server/docs/src/myserver/file.html b/scripts/03_server/docs/src/myserver/file.html index 804b469..b7853b6 100644 --- a/scripts/03_server/docs/src/myserver/file.html +++ b/scripts/03_server/docs/src/myserver/file.html @@ -31,10 +31,13 @@

    API Documentation

    @@ -52,147 +55,131 @@

    src.myserver.file

    - +

    A package for learning network programming in Python.

    + +

    This module (file) manages operations relative to the file system.

    +
    + -
     1import os.path as path
    - 2
    - 3_404_CONTENT = """<html>
    - 4<body>
    - 5    <h1>Erreur 404</h1>
    - 6    <p>Vous avez traversé les limites du Web. Où que vous soyez, ce n'est sur aucune carte.</p>
    - 7</body>
    - 8</html>
    - 9""".encode()
    -10
    -11_500_CONTENT = """<html>
    -12<body>
    -13    <h1>Erreur 500 : InTERNal SRveR ER0ooOR</h1>
    -14    <p>Ça marche pas.</p>
    -15</body>
    -16</html>
    -17""".encode()
    -18
    -19
    -20
    -21def get_resource(res: str, root: str):
    -22    """Returns the resource requested by a GET request.
    -23
    -24    Parameters
    -25    ----------
    -26    - res: str
    -27        Requested resource string.
    -28    - root: str
    -29        Root directory of the server.
    -30
    -31    Returns
    -32    -------
    -33    int 
    -34        The response HTTP code.
    -35    bytes
    -36        The resource content.
    -37    """
    -38
    -39    code, res_path = resolve_location(res, root)
    -40    if code == 404:
    -41        return 404, _404_CONTENT
    -42    elif code != 200:
    -43        return 500, _500_CONTENT
    -44
    -45    print(f"Opening file {res_path}...")
    -46    with open(res_path, 'rb') as f:
    -47        content = f.read()
    -48        return 200, content
    -49
    -50    
    -51def resolve_location(res:str, root: str):
    -52    assert len(res) != 0
    -53    
    -54    # Resolve the home directory if it is in the root 
    -55    root = path.expanduser(root)
    -56
    -57    if res[-1] == "/":
    -58        res += "index.html"
    -59    if res[0] == "/":
    -60        res = res[1:]
    -61
    -62    res_path = path.join(root, res)
    -63    
    -64    if not (path.exists(res_path) and path.isfile(res_path)):
    -65        return 404, ""
    -66    # if path.isdir(res_path):
    -67    #     if not path.exists(res_path):
    -68    #         return 404, _404_CONTENT
    -69    return 200, res_path
    +                        
      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
    + 17import os.path as path
    + 18
    + 19
    + 20def resolve_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    """
    + 41    try:
    + 42        res_path = resolve_path(res, root)
    + 43
    + 44        # Compute file extension
    + 45        extension = path.splitext(res_path)[1]
    + 46        # Maybe remove leading '.'
    + 47        if len(extension) > 0 and extension[0] == '.':
    + 48            extension = extension[1:]
    + 49
    + 50        return res_path, extension
    + 51    except Exception: 
    + 52        return "", ""
    + 53
    + 54def resolve_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    """
    + 73    try:
    + 74        # Resolve the home directory if it is in the root
    + 75        root = path.expanduser(root)
    + 76
    + 77        if res[-1] == "/":
    + 78            res += "index.html"
    + 79        while res[0] == "/":
    + 80            res = res[1:]
    + 81
    + 82        res_path = path.join(root, res)
    + 83        
    + 84        if not (path.exists(res_path) and path.isfile(res_path)):
    + 85            return ""
    + 86        return res_path
    + 87    except Exception: 
    + 88        return ""
    + 89
    + 90
    + 91
    + 92def get_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
    +109    print(f"Opening file {res_path}...")
    +110    with open(res_path, 'rb') as f:
    +111        content = f.read()
    +112        return content, 200
     
    -
    - -
    - - def - get_resource(res: str, root: str): - - - -
    - -
    22def get_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
    -40    code, res_path = resolve_location(res, root)
    -41    if code == 404:
    -42        return 404, _404_CONTENT
    -43    elif code != 200:
    -44        return 500, _500_CONTENT
    -45
    -46    print(f"Opening file {res_path}...")
    -47    with open(res_path, 'rb') as f:
    -48        content = f.read()
    -49        return 200, 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
    -
    52def resolve_location(res:str, root: str):
    -53    assert len(res) != 0
    -54    
    -55    # Resolve the home directory if it is in the root 
    -56    root = path.expanduser(root)
    -57
    -58    if res[-1] == "/":
    -59        res += "index.html"
    -60    if res[0] == "/":
    -61        res = res[1:]
    -62
    -63    res_path = path.join(root, res)
    -64    
    -65    if not (path.exists(res_path) and path.isfile(res_path)):
    -66        return 404, ""
    -67    # if path.isdir(res_path):
    -68    #     if not path.exists(res_path):
    -69    #         return 404, _404_CONTENT
    -70    return 200, res_path
    +            
    21def resolve_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    """
    +42    try:
    +43        res_path = resolve_path(res, root)
    +44
    +45        # Compute file extension
    +46        extension = path.splitext(res_path)[1]
    +47        # Maybe remove leading '.'
    +48        if len(extension) > 0 and extension[0] == '.':
    +49            extension = extension[1:]
    +50
    +51        return res_path, extension
    +52    except Exception: 
    +53        return "", ""
     
    - +

    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 "".

    +
    + + +
    +
    + +
    + + def + resolve_path(res: str, root: str): + + + +
    + +
    55def resolve_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    """
    +74    try:
    +75        # Resolve the home directory if it is in the root
    +76        root = path.expanduser(root)
    +77
    +78        if res[-1] == "/":
    +79            res += "index.html"
    +80        while res[0] == "/":
    +81            res = res[1:]
    +82
    +83        res_path = path.join(root, res)
    +84        
    +85        if not (path.exists(res_path) and path.isfile(res_path)):
    +86            return ""
    +87        return res_path
    +88    except Exception: 
    +89        return ""
    +
    + + +

    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): + + + +
    + +
     93def get_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
    +110    print(f"Opening file {res_path}...")
    +111    with open(res_path, 'rb') as f:
    +112        content = f.read()
    +113        return content, 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.

    +
    +
    diff --git a/scripts/03_server/docs/src/myserver/http.html b/scripts/03_server/docs/src/myserver/http.html new file mode 100644 index 0000000..91a690c --- /dev/null +++ b/scripts/03_server/docs/src/myserver/http.html @@ -0,0 +1,574 @@ + + + + + + + src.myserver.http API documentation + + + + + + + + + +
    +
    +

    +src.myserver.http

    + +

    A package for learning network programming in Python.

    + +

    This module (file) provides information relative to the HTTP specification.

    +
    + + + + + +
      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) provides information relative to the HTTP specification.
    + 15"""
    + 16
    + 17
    + 18def get_http_code(code: int):
    + 19    """Returns a dict corresponding to the HTTP status code.
    + 20
    + 21    See also : https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
    + 22    
    + 23    Parameters
    + 24    ----------
    + 25    - code: int 
    + 26        An HTTP code.
    + 27
    + 28    Returns
    + 29    -------
    + 30    dict 
    + 31        Information about the HTTP code, containing fileds:
    + 32        - header: str 
    + 33            The code string to put in an HTTP reply header.
    + 34        - html: str 
    + 35            The HTML to reply as HTTP content.
    + 36    """
    + 37
    + 38    if code == 200:
    + 39        return {
    + 40            "header": "200 OK",
    + 41            "html": ""
    + 42        }
    + 43    elif code == 403:
    + 44        return {
    + 45            "header": "403 Forbidden",
    + 46            "html": """<html>
    + 47<body>
    + 48    <h1>Erreur 403 : Interdit</h1>
    + 49    <p>Une porte fermée se tient devant vous ; et vous n'avez pas la clé.</p>
    + 50</body>
    + 51</html>
    + 52"""
    + 53        }
    + 54    elif code == 404:
    + 55        return {
    + 56            "header": "404 Not Found",
    + 57            "html": """<html>
    + 58<body>
    + 59    <h1>Erreur 404</h1>
    + 60    <p>Vous avez traversé les limites du Web. Où que vous soyez, ce n'est sur aucune carte.</p>
    + 61</body>
    + 62</html>
    + 63"""
    + 64        }
    + 65    elif code == 501:
    + 66        return {
    + 67            "header": "501 Not implemented",
    + 68            "html": """<html>
    + 69<body>
    + 70    <h1>Erreur 501 : Non implémenté</h1>
    + 71    <p>Ce que vous demandez est acceptable, mais on ne fait pas ça chez nous.</p>
    + 72</body>
    + 73</html>
    + 74"""
    + 75        }
    + 76    else: # 500
    + 77        return {
    + 78            "header": "500 Internal Server Error",
    + 79            "html": """<html>
    + 80<body>
    + 81    <h1>Erreur 500 : InTERNal SRveR ER0ooOR</h1>
    + 82    <p>Erreur serveur inconnue.</p>
    + 83</body>
    + 84</html>
    + 85"""
    + 86        }
    + 87
    + 88# From: https://source.chromium.org/chromium/chromium/src/+/main:net/base/mime_util.cc;l=147
    + 89# The Chromium authors, 2012, BSD Licence
    + 90file_extension_to_content_type = {
    + 91    "webm": "video/webm",
    + 92    "mp3": "audio/mpeg",
    + 93    "wasm": "application/wasm",
    + 94    "crx": "application/x-chrome-extension",
    + 95    "xhtml": "application/xhtml+xml",
    + 96    "xht": "application/xhtml+xml",
    + 97    "xhtm": "application/xhtml+xml",
    + 98    "flac": "audio/flac",
    + 99    "ogg": "audio/ogg",
    +100    "oga": "audio/ogg",
    +101    "opus": "audio/ogg",
    +102    "wav": "audio/wav",
    +103    "m4a": "audio/x-m4a",
    +104    "avif": "image/avif",
    +105    "gif": "image/gif",
    +106    "jpeg": "image/jpeg",
    +107    "jpg": "image/jpeg",
    +108    "png": "image/png",
    +109    "apng": "image/apng",
    +110    "svg": "image/svg+xml",
    +111    "svgz": "image/svg+xml",
    +112    "webp": "image/webp",
    +113    "mht": "multipart/related",
    +114    "mhtml": "multipart/related",
    +115    "css": "text/css",
    +116    "html": "text/html",
    +117    "htm": "text/html",
    +118    "shtml": "text/html",
    +119    "shtm": "text/html",
    +120    "js": "text/javascript",
    +121    "mjs": "text/javascript",
    +122    "xml": "text/xml",
    +123    "mp4": "video/mp4",
    +124    "m4v": "video/mp4",
    +125    "ogv": "video/ogg",
    +126    "ogm": "video/ogg",
    +127    "csv": "text/csv",
    +128    "ico": "image/vnd.microsoft.icon"
    +129}
    +130
    +131def get_http_content_type(extension: str):
    +132    """Returns the HTTP Content-Type corresponding to a file extension.
    +133
    +134    Returns "application/octet-stream" when the extension is unknown.
    +135
    +136    Parameters
    +137    ----------
    +138    - extension: str
    +139        A file extension.
    +140
    +141    Returns
    +142    -------
    +143    str 
    +144        An HTTP Content-Type 
    +145    """
    +146
    +147    if file_extension_to_content_type.get(extension) is None:
    +148        return "application/octet-stream"
    +149    return file_extension_to_content_type[extension]
    +
    + + +
    +
    + +
    + + def + get_http_code(code: int): + + + +
    + +
    19def get_http_code(code: int):
    +20    """Returns a dict corresponding to the HTTP status code.
    +21
    +22    See also : https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
    +23    
    +24    Parameters
    +25    ----------
    +26    - code: int 
    +27        An HTTP code.
    +28
    +29    Returns
    +30    -------
    +31    dict 
    +32        Information about the HTTP code, containing fileds:
    +33        - header: str 
    +34            The code string to put in an HTTP reply header.
    +35        - html: str 
    +36            The HTML to reply as HTTP content.
    +37    """
    +38
    +39    if code == 200:
    +40        return {
    +41            "header": "200 OK",
    +42            "html": ""
    +43        }
    +44    elif code == 403:
    +45        return {
    +46            "header": "403 Forbidden",
    +47            "html": """<html>
    +48<body>
    +49    <h1>Erreur 403 : Interdit</h1>
    +50    <p>Une porte fermée se tient devant vous ; et vous n'avez pas la clé.</p>
    +51</body>
    +52</html>
    +53"""
    +54        }
    +55    elif code == 404:
    +56        return {
    +57            "header": "404 Not Found",
    +58            "html": """<html>
    +59<body>
    +60    <h1>Erreur 404</h1>
    +61    <p>Vous avez traversé les limites du Web. Où que vous soyez, ce n'est sur aucune carte.</p>
    +62</body>
    +63</html>
    +64"""
    +65        }
    +66    elif code == 501:
    +67        return {
    +68            "header": "501 Not implemented",
    +69            "html": """<html>
    +70<body>
    +71    <h1>Erreur 501 : Non implémenté</h1>
    +72    <p>Ce que vous demandez est acceptable, mais on ne fait pas ça chez nous.</p>
    +73</body>
    +74</html>
    +75"""
    +76        }
    +77    else: # 500
    +78        return {
    +79            "header": "500 Internal Server Error",
    +80            "html": """<html>
    +81<body>
    +82    <h1>Erreur 500 : InTERNal SRveR ER0ooOR</h1>
    +83    <p>Erreur serveur inconnue.</p>
    +84</body>
    +85</html>
    +86"""
    +87        }
    +
    + + +

    Returns a dict corresponding to the HTTP status code.

    + +

    See also : https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

    + +

    Parameters

    + +
      +
    • code: int +An HTTP code.
    • +
    + +

    Returns

    + +

    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.

    +
    + + +
    +
    +
    + file_extension_to_content_type = + + {'webm': 'video/webm', 'mp3': 'audio/mpeg', 'wasm': 'application/wasm', 'crx': 'application/x-chrome-extension', 'xhtml': 'application/xhtml+xml', 'xht': 'application/xhtml+xml', 'xhtm': 'application/xhtml+xml', 'flac': 'audio/flac', 'ogg': 'audio/ogg', 'oga': 'audio/ogg', 'opus': 'audio/ogg', 'wav': 'audio/wav', 'm4a': 'audio/x-m4a', 'avif': 'image/avif', 'gif': 'image/gif', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'png': 'image/png', 'apng': 'image/apng', 'svg': 'image/svg+xml', 'svgz': 'image/svg+xml', 'webp': 'image/webp', 'mht': 'multipart/related', 'mhtml': 'multipart/related', 'css': 'text/css', 'html': 'text/html', 'htm': 'text/html', 'shtml': 'text/html', 'shtm': 'text/html', 'js': 'text/javascript', 'mjs': 'text/javascript', 'xml': 'text/xml', 'mp4': 'video/mp4', 'm4v': 'video/mp4', 'ogv': 'video/ogg', 'ogm': 'video/ogg', 'csv': 'text/csv', 'ico': 'image/vnd.microsoft.icon'} + + +
    + + + + +
    +
    + +
    + + def + get_http_content_type(extension: str): + + + +
    + +
    132def get_http_content_type(extension: str):
    +133    """Returns the HTTP Content-Type corresponding to a file extension.
    +134
    +135    Returns "application/octet-stream" when the extension is unknown.
    +136
    +137    Parameters
    +138    ----------
    +139    - extension: str
    +140        A file extension.
    +141
    +142    Returns
    +143    -------
    +144    str 
    +145        An HTTP Content-Type 
    +146    """
    +147
    +148    if file_extension_to_content_type.get(extension) is None:
    +149        return "application/octet-stream"
    +150    return file_extension_to_content_type[extension]
    +
    + + +

    Returns the HTTP Content-Type corresponding to a file extension.

    + +

    Returns "application/octet-stream" when the extension is unknown.

    + +

    Parameters

    + +
      +
    • extension: str +A file extension.
    • +
    + +

    Returns

    + +

    str + An HTTP Content-Type

    +
    + + +
    +
    + + \ No newline at end of file diff --git a/scripts/03_server/docs/src/myserver/http_request.html b/scripts/03_server/docs/src/myserver/http_request.html index 0c0b29f..6f438ce 100644 --- a/scripts/03_server/docs/src/myserver/http_request.html +++ b/scripts/03_server/docs/src/myserver/http_request.html @@ -55,9 +55,9 @@

    src.myserver.http_request

    -

    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 - 17def parse_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 """ - 43 if buf == b'': - 44 raise ValueError("Received empty request") - 45 lines = buf.decode('utf-8').strip().splitlines() - 46 - 47 req_head = parse_request_head(lines[0]) - 48 req_params = dict() - 49 if len(lines) > 1: - 50 req_params = parse_request_params(lines[1:]) - 51 - 52 return dict( - 53 head=req_head, - 54 params=req_params - 55 ) - 56 - 57def parse_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 """ - 83 fields = line.split(' ') - 84 if len(fields) != 3: - 85 raise ValueError(f"Request header is invalid: {line}") - 86 - 87 return dict( - 88 verb=fields[0].upper(), - 89 resource=fields[1] - 90 ) - 91 - 92def parse_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 """ -117 params = dict() -118 for l in lines: -119 kv = l.strip().split(': ') -120 -121 if len(kv) != 2 or len(kv[0]) == 0 or len(kv[1]) == 0: -122 raise ValueError(f"Request line is not a valid key/value pair: {l}") -123 -124 params[kv[0]] = kv[1] -125 return params + 17# TODO: Gestion de la casse des paramètres ? + 18 + 19def parse_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 """ + 45 if buf == b'': + 46 raise ValueError("Received empty request") + 47 lines = buf.decode('utf-8').strip().splitlines() + 48 + 49 req_head = parse_request_head(lines[0]) + 50 req_params = dict() + 51 if len(lines) > 1: + 52 req_params = parse_request_params(lines[1:]) + 53 + 54 return dict( + 55 head=req_head, + 56 params=req_params + 57 ) + 58 + 59def parse_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 """ + 85 fields = line.split(' ') + 86 if len(fields) != 3: + 87 raise ValueError(f"Request header is invalid: {line}") + 88 + 89 return dict( + 90 verb=fields[0].upper(), + 91 resource=fields[1] + 92 ) + 93 + 94def parse_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 """ +119 params = dict() +120 for l in lines: +121 kv = l.strip().split(': ') +122 +123 if len(kv) != 2 or len(kv[0]) == 0 or len(kv[1]) == 0: +124 raise ValueError(f"Request line is not a valid key/value pair: {l}") +125 +126 params[kv[0]] = kv[1] +127 return params
    @@ -204,45 +206,45 @@
    -
    18def parse_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    """
    -44    if buf == b'':
    -45        raise ValueError("Received empty request")
    -46    lines = buf.decode('utf-8').strip().splitlines()
    -47
    -48    req_head = parse_request_head(lines[0])
    -49    req_params = dict()
    -50    if len(lines) > 1:
    -51        req_params = parse_request_params(lines[1:])
    -52
    -53    return dict(
    -54        head=req_head,
    -55        params=req_params
    -56    )
    +            
    20def parse_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    """
    +46    if buf == b'':
    +47        raise ValueError("Received empty request")
    +48    lines = buf.decode('utf-8').strip().splitlines()
    +49
    +50    req_head = parse_request_head(lines[0])
    +51    req_params = dict()
    +52    if len(lines) > 1:
    +53        req_params = parse_request_params(lines[1:])
    +54
    +55    return dict(
    +56        head=req_head,
    +57        params=req_params
    +58    )
     
    @@ -290,40 +292,40 @@ The HTTP request buffer.
    -
    58def parse_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    """
    -84    fields = line.split(' ')
    -85    if len(fields) != 3:
    -86        raise ValueError(f"Request header is invalid: {line}")
    -87
    -88    return dict(
    -89        verb=fields[0].upper(),
    -90        resource=fields[1]
    -91    )
    +            
    60def parse_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    """
    +86    fields = line.split(' ')
    +87    if len(fields) != 3:
    +88        raise ValueError(f"Request header is invalid: {line}")
    +89
    +90    return dict(
    +91        verb=fields[0].upper(),
    +92        resource=fields[1]
    +93    )
     
    @@ -371,40 +373,40 @@ The HTTP request header (the first line of a full HTTP request).
    -
     93def parse_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    """
    -118    params = dict()
    -119    for l in lines:
    -120        kv = l.strip().split(': ')
    -121        
    -122        if len(kv) != 2 or len(kv[0]) == 0 or len(kv[1]) == 0:
    -123            raise ValueError(f"Request line is not a valid key/value pair: {l}")
    -124
    -125        params[kv[0]] = kv[1]
    -126    return params
    +            
     95def parse_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    """
    +120    params = dict()
    +121    for l in lines:
    +122        kv = l.strip().split(': ')
    +123        
    +124        if len(kv) != 2 or len(kv[0]) == 0 or len(kv[1]) == 0:
    +125            raise ValueError(f"Request line is not a valid key/value pair: {l}")
    +126
    +127        params[kv[0]] = kv[1]
    +128    return params
     
    diff --git a/scripts/03_server/docs/src/myserver/log.html b/scripts/03_server/docs/src/myserver/log.html index a5a8db5..5cd38b1 100644 --- a/scripts/03_server/docs/src/myserver/log.html +++ b/scripts/03_server/docs/src/myserver/log.html @@ -58,9 +58,9 @@

    src.myserver.log

    -

    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 HTTP logging messages.

    @@ -78,9 +78,9 @@ 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 HTTP logging messages. 15""" 16 17from myserver.date import now_rfc2616 @@ -143,9 +143,9 @@ 74 75def log_reply(addr: tuple[str, int], req: dict[str, dict], code: int): 76 """ - 77 Logs a reply message to stdout, with a timestamp and an address (host:port). + 77 Logs an HTTP reply to stdout, with timestamp, address (host:port), code, UA. 78 - 79 Output is: `timestamp - host:port - message`. + 79 Output is: `timestamp - host:port - HTTP-verb HTTP-resource - code - message`. 80 81 Parameters 82 ---------- @@ -324,9 +324,9 @@ The request to print.
     76def log_reply(addr: tuple[str, int], req: dict[str, dict], code: int):
      77    """
    - 78    Logs a reply message to stdout, with a timestamp and an address (host:port).
    + 78    Logs an HTTP reply to stdout, with timestamp, address (host:port), code, UA.
      79    
    - 80    Output is: `timestamp - host:port - message`.
    + 80    Output is: `timestamp - host:port - HTTP-verb HTTP-resource - code - message`.
      81    
      82    Parameters
      83    ----------
    @@ -351,9 +351,9 @@ The request to print.
     
    -

    Logs a reply message to stdout, with a timestamp and an address (host:port).

    +

    Logs an HTTP reply to stdout, with timestamp, address (host:port), code, UA.

    -

    Output is: timestamp - host:port - message.

    +

    Output is: timestamp - host:port - HTTP-verb HTTP-resource - code - message.

    Parameters

    diff --git a/scripts/03_server/docs/src/myserver/server.html b/scripts/03_server/docs/src/myserver/server.html index 428d94e..05445a3 100644 --- a/scripts/03_server/docs/src/myserver/server.html +++ b/scripts/03_server/docs/src/myserver/server.html @@ -37,7 +37,10 @@ handle_client
  • - reply + prepare_resource +
  • +
  • + prepare_reply
  • @@ -55,94 +58,198 @@

    src.myserver.server

    -

    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
    -17import socket 
    -18from myserver.log import log, log_reply
    -19from myserver.http_request import parse_request
    -20from myserver.file import get_resource
    -21from myserver.date import now_rfc2616 
    -22
    -23_BUF_SIZE = 1024 
    -24_SERVER_ADDR = "0.0.0.0"
    -25
    -26def serve(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    """
    -33    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    -34
    -35    # Allows reusing a socket right after it got closed (after ctrl-c)
    -36    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    -37
    -38    s.bind((_SERVER_ADDR, port))
    -39    s.listen()
    -40
    -41    log(f"Server started at {_SERVER_ADDR}:{port}.")
    -42
    -43    try: # Catch KeyboardInterrupt to close server socket
    -44        while True:
    -45            c, addr = s.accept()
    -46            try: # Catch KeyboardInterrupt to close client handling socket
    -47                handle_client(c, addr, root)
    -48            except KeyboardInterrupt as e:
    -49                c.close()
    -50                raise e
    -51    except KeyboardInterrupt:
    -52        s.close()
    -53
    -54def handle_client(c: socket.socket, addr: tuple[str, int], root:str):
    -55    buf = c.recv(_BUF_SIZE)
    -56    req = parse_request(buf)
    -57
    -58    if req['head']['verb'] == 'GET':
    -59       code, content = get_resource(req['head']['resource'], root) 
    -60       reply(c, addr, req, code, content)
    -61
    -62    c.close()
    -63
    -64def reply(c: socket.socket, addr: tuple[str, int], req: dict, code: int, content: bytes):
    -65    return_message = "200 OK"
    -66    if code == 404:
    -67        return_message = "404 Not found"
    -68
    -69    # TODO: Change content type depending on actual content type
    -70    reply_header = f"""HTTP/1.1 {return_message}
    -71Content-Type: text/html; charset=utf-8
    -72Date: {now_rfc2616()}
    -73Content-Length: {len(content)}
    -74
    -75"""
    -76    c.send(reply_header.encode())
    -77    c.send(content)
    -78
    -79    log_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
    + 17import socket 
    + 18from myserver.log import log, log_reply
    + 19from myserver.http_request import parse_request
    + 20from myserver.file import resolve_location, get_resource
    + 21from myserver.date import now_rfc2616 
    + 22from myserver.http import get_http_code, get_http_content_type
    + 23
    + 24_BUF_SIZE = 4096
    + 25_SERVER_ADDR = "0.0.0.0"
    + 26
    + 27def serve(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    """
    + 34    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    + 35
    + 36    # Allows reusing a socket right after it got closed (after ctrl-c)
    + 37    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    + 38
    + 39    # Attach the socket to the port and interface provided.
    + 40    s.bind((_SERVER_ADDR, port))
    + 41
    + 42    ## Start listening on the socket.
    + 43    s.listen()
    + 44
    + 45    log(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.
    + 49    try: 
    + 50        while True:
    + 51            c, 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.
    + 54            try: 
    + 55                handle_client(c, addr, root)
    + 56            # If the KeyboardInterrupt is raised, we pass it to the outer loop
    + 57            # to also close the server socket.
    + 58            except KeyboardInterrupt as e:
    + 59                c.close()
    + 60                raise e
    + 61    except KeyboardInterrupt:
    + 62        log("Received KeyboardInterrupt. Closing...")
    + 63        s.close()
    + 64
    + 65        
    + 66def handle_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    """
    + 87    buf = c.recv(_BUF_SIZE)
    + 88    req = parse_request(buf)
    + 89
    + 90    # Prepare our reply.
    + 91    if req['head']['verb'] == 'GET':
    + 92        reply, code = prepare_resource(root, req)
    + 93    else:
    + 94        # Not implemented: we treat only GET calls for now.
    + 95        reply, code = prepare_reply(b"", "", 501)
    + 96
    + 97    # Send the reply back.
    + 98    c.send(reply)
    + 99
    +100    # Trace the action in the logs.
    +101    log_reply(addr, req, code)
    +102
    +103    # Close the connection.
    +104    c.close()
    +105    
    +106
    +107def prepare_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    """
    +127    code = 200
    +128    content = b""
    +129    content_type = ""
    +130
    +131    res_path, res_extension = resolve_location(req['head']['resource'], root)
    +132    if res_path == "":
    +133        code = 404
    +134    else:
    +135        content_type = get_http_content_type(res_extension)
    +136        content, code = get_resource(res_path)
    +137
    +138    return prepare_reply(content, content_type, code)
    +139
    +140
    +141def prepare_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
    +169    http_code_dict = get_http_code(code)
    +170    if code != 200:
    +171        content = http_code_dict['html'].encode()
    +172        content_type = get_http_content_type('html')+"; charset=utf-8"
    +173
    +174    # Prepare header
    +175    header = 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
    +183    return header + content, code 
     
    @@ -158,40 +265,50 @@
    -
    27def serve(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    """
    -34    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    -35
    -36    # Allows reusing a socket right after it got closed (after ctrl-c)
    -37    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    -38
    -39    s.bind((_SERVER_ADDR, port))
    -40    s.listen()
    -41
    -42    log(f"Server started at {_SERVER_ADDR}:{port}.")
    -43
    -44    try: # Catch KeyboardInterrupt to close server socket
    -45        while True:
    -46            c, addr = s.accept()
    -47            try: # Catch KeyboardInterrupt to close client handling socket
    -48                handle_client(c, addr, root)
    -49            except KeyboardInterrupt as e:
    -50                c.close()
    -51                raise e
    -52    except KeyboardInterrupt:
    -53        s.close()
    +            
    28def serve(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    """
    +35    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    +36
    +37    # Allows reusing a socket right after it got closed (after ctrl-c)
    +38    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    +39
    +40    # Attach the socket to the port and interface provided.
    +41    s.bind((_SERVER_ADDR, port))
    +42
    +43    ## Start listening on the socket.
    +44    s.listen()
    +45
    +46    log(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.
    +50    try: 
    +51        while True:
    +52            c, 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.
    +55            try: 
    +56                handle_client(c, addr, root)
    +57            # If the KeyboardInterrupt is raised, we pass it to the outer loop
    +58            # to also close the server socket.
    +59            except KeyboardInterrupt as e:
    +60                c.close()
    +61                raise e
    +62    except KeyboardInterrupt:
    +63        log("Received KeyboardInterrupt. Closing...")
    +64        s.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()().

    @@ -207,52 +324,231 @@
    -
    55def handle_client(c: socket.socket, addr: tuple[str, int], root:str):
    -56    buf = c.recv(_BUF_SIZE)
    -57    req = parse_request(buf)
    -58
    -59    if req['head']['verb'] == 'GET':
    -60       code, content = get_resource(req['head']['resource'], root) 
    -61       reply(c, addr, req, code, content)
    -62
    -63    c.close()
    +            
     67def handle_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    """
    + 88    buf = c.recv(_BUF_SIZE)
    + 89    req = parse_request(buf)
    + 90
    + 91    # Prepare our reply.
    + 92    if req['head']['verb'] == 'GET':
    + 93        reply, code = prepare_resource(root, req)
    + 94    else:
    + 95        # Not implemented: we treat only GET calls for now.
    + 96        reply, code = prepare_reply(b"", "", 501)
    + 97
    + 98    # Send the reply back.
    + 99    c.send(reply)
    +100
    +101    # Trace the action in the logs.
    +102    log_reply(addr, req, code)
    +103
    +104    # Close the connection.
    +105    c.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.
    • +
    +
    + -
    - +
    +
    def - reply( c: socket.socket, addr: tuple[str, int], req: dict, code: int, content: bytes): + prepare_resource(root: str, req: dict): - +
    - -
    65def reply(c: socket.socket, addr: tuple[str, int], req: dict, code: int, content: bytes):
    -66    return_message = "200 OK"
    -67    if code == 404:
    -68        return_message = "404 Not found"
    -69
    -70    # TODO: Change content type depending on actual content type
    -71    reply_header = f"""HTTP/1.1 {return_message}
    -72Content-Type: text/html; charset=utf-8
    -73Date: {now_rfc2616()}
    -74Content-Length: {len(content)}
    -75
    -76"""
    -77    c.send(reply_header.encode())
    -78    c.send(content)
    -79
    -80    log_reply(addr, req, code)
    +    
    +            
    108def prepare_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    """
    +128    code = 200
    +129    content = b""
    +130    content_type = ""
    +131
    +132    res_path, res_extension = resolve_location(req['head']['resource'], root)
    +133    if res_path == "":
    +134        code = 404
    +135    else:
    +136        content_type = get_http_content_type(res_extension)
    +137        content, code = get_resource(res_path)
    +138
    +139    return prepare_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.

    +
    + + +
    +
    + +
    + + def + prepare_reply(content: bytes, content_type: str, code: int): + + + +
    + +
    142def prepare_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
    +170    http_code_dict = get_http_code(code)
    +171    if code != 200:
    +172        content = http_code_dict['html'].encode()
    +173        content_type = get_http_content_type('html')+"; charset=utf-8"
    +174
    +175    # Prepare header
    +176    header = 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
    +184    return header + content, code 
    +
    + + +

    Generates the proper answer, including the HTTP headers and content of the +webpage, and the status code.

    + +

    For more information about:

    + + + +

    Parameters

    + +
      +
    • content: bytes +The raw data for the resource.
    • +
    • 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.

    +
    +
    diff --git a/scripts/03_server/docs/tests.html b/scripts/03_server/docs/tests.html index fbd3fd3..57addaa 100644 --- a/scripts/03_server/docs/tests.html +++ b/scripts/03_server/docs/tests.html @@ -31,6 +31,7 @@

    Submodules

      +
    • test_file
    • test_http_request
    • test_log
    • test_server
    • diff --git a/scripts/03_server/docs/tests/test_file.html b/scripts/03_server/docs/tests/test_file.html new file mode 100644 index 0000000..ccc810f --- /dev/null +++ b/scripts/03_server/docs/tests/test_file.html @@ -0,0 +1,486 @@ + + + + + + + tests.test_file API documentation + + + + + + + + + +
      +
      +

      +tests.test_file

      + +

      Script to test the myserver module.

      +
      + + + + + +
       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
      +15import pytest
      +16import sys
      +17from os.path import dirname, join
      +18
      +19from myserver.file import resolve_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    """
      +26    tests_dir = dirname(__file__)
      +27    return tests_dir
      +28
      +29
      +30def test_resolve_path():
      +31    """
      +32    Tests the resolve_path() function with some normal use cases.
      +33    """
      +34    tests_path = _find_root()
      +35    res_path = join(tests_path, 'resources', 'www')
      +36
      +37    # Normal resource.
      +38    full_path = resolve_path(res='/index.html', root=res_path)
      +39    print(full_path)
      +40    assert full_path.endswith('resources/www/index.html')
      +41
      +42    # Let's try with a folder in resource, with an image.
      +43    full_path = resolve_path(res='/images/chaton.jpg', root=res_path)
      +44    print(full_path)
      +45    assert full_path.endswith('resources/www/images/chaton.jpg')
      +46
      +47
      +48def test_resolve_path_weirdos():
      +49    """
      +50    Tests the resolve_path() function with some abnormal use cases.
      +51    """
      +52    tests_path = _find_root()
      +53    res_path = join(tests_path, 'resources', 'www')
      +54
      +55    # //index.html should be the same as /index.html.
      +56    full_path = resolve_path(res='//index.html', root=res_path)
      +57    print(full_path)
      +58    assert full_path.endswith('resources/www/index.html')
      +59
      +60    # / should return /index.html path
      +61    full_path = resolve_path(res='/', root=res_path)
      +62    print(full_path)
      +63    assert full_path.endswith('resources/www/index.html')
      +64
      +65    # Non-existing resources should return the empty string ''.
      +66    full_path = resolve_path(res='/index.dontexist', root=res_path)
      +67    print(full_path)
      +68    assert full_path == ''
      +69
      +70    
      +71def test_get_resource():
      +72    """
      +73    Tests the get_resource() function by checking the content of the returned file.
      +74    """
      +75    tests_path = _find_root()
      +76    res_path = join(tests_path, 'resources', 'www', 'index.html')
      +77    out = get_resource(res_path=res_path)
      +78    print(out)
      +79    assert out[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
      +81def test_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    """
      +88    tests_path = _find_root()
      +89    res_path = join(tests_path, 'resources', 'www', 'images', 'chaton.jpg')
      +90    out = get_resource(res_path=res_path)
      +91    print(out[0][:4])
      +92    assert out[0][:4] == b'\xff\xd8\xff\xe0'
      +93
      +94    
      +
      + + +
      +
      + +
      + + def + test_resolve_path(): + + + +
      + +
      31def test_resolve_path():
      +32    """
      +33    Tests the resolve_path() function with some normal use cases.
      +34    """
      +35    tests_path = _find_root()
      +36    res_path = join(tests_path, 'resources', 'www')
      +37
      +38    # Normal resource.
      +39    full_path = resolve_path(res='/index.html', root=res_path)
      +40    print(full_path)
      +41    assert full_path.endswith('resources/www/index.html')
      +42
      +43    # Let's try with a folder in resource, with an image.
      +44    full_path = resolve_path(res='/images/chaton.jpg', root=res_path)
      +45    print(full_path)
      +46    assert full_path.endswith('resources/www/images/chaton.jpg')
      +
      + + +

      Tests the resolve_path() function with some normal use cases.

      +
      + + +
      +
      + +
      + + def + test_resolve_path_weirdos(): + + + +
      + +
      49def test_resolve_path_weirdos():
      +50    """
      +51    Tests the resolve_path() function with some abnormal use cases.
      +52    """
      +53    tests_path = _find_root()
      +54    res_path = join(tests_path, 'resources', 'www')
      +55
      +56    # //index.html should be the same as /index.html.
      +57    full_path = resolve_path(res='//index.html', root=res_path)
      +58    print(full_path)
      +59    assert full_path.endswith('resources/www/index.html')
      +60
      +61    # / should return /index.html path
      +62    full_path = resolve_path(res='/', root=res_path)
      +63    print(full_path)
      +64    assert full_path.endswith('resources/www/index.html')
      +65
      +66    # Non-existing resources should return the empty string ''.
      +67    full_path = resolve_path(res='/index.dontexist', root=res_path)
      +68    print(full_path)
      +69    assert full_path == ''
      +
      + + +

      Tests the resolve_path() function with some abnormal use cases.

      +
      + + +
      +
      + +
      + + def + test_get_resource(): + + + +
      + +
      72def test_get_resource():
      +73    """
      +74    Tests the get_resource() function by checking the content of the returned file.
      +75    """
      +76    tests_path = _find_root()
      +77    res_path = join(tests_path, 'resources', 'www', 'index.html')
      +78    out = get_resource(res_path=res_path)
      +79    print(out)
      +80    assert out[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(): + + + +
      + +
      82def test_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    """
      +89    tests_path = _find_root()
      +90    res_path = join(tests_path, 'resources', 'www', 'images', 'chaton.jpg')
      +91    out = get_resource(res_path=res_path)
      +92    print(out[0][:4])
      +93    assert out[0][:4] == b'\xff\xd8\xff\xe0'
      +
      + + +

      Tests the get_resource() function by checking the content of an image.

      + +

      For an image, check we get the magic numbers for a JPEG. +See https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5 for a complete list.

      +
      + + +
      +
      + + \ No newline at end of file diff --git a/scripts/03_server/docs/tests/test_log.html b/scripts/03_server/docs/tests/test_log.html index 3a95456..2be0d46 100644 --- a/scripts/03_server/docs/tests/test_log.html +++ b/scripts/03_server/docs/tests/test_log.html @@ -36,6 +36,21 @@
    • test_log_address
    • +
    • + test_log_request +
    • +
    • + test_log_request_useragent +
    • +
    • + test_log_request_failed_assert +
    • +
    • + test_log_reply +
    • +
    • + test_log_reply_useragent +
    @@ -59,49 +74,159 @@ -
     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
    -15import pytest
    -16import io
    -17from contextlib import redirect_stdout
    -18
    -19from myserver.log import log, log_address, log_request, log_reply
    -20
    -21
    -22def test_log():
    -23    """
    -24    Tests the log() function by capturing stdout.
    -25    """
    -26    f = io.StringIO()
    -27    with redirect_stdout(f):
    -28        log('test message')
    -29    out = f.getvalue()
    -30    print(out)
    -31    assert out.endswith(' - test message\n')
    -32
    -33    
    -34def test_log_address():
    -35    """
    -36    Tests the log_address() function by capturing stdout.
    -37    """
    -38    f = io.StringIO()
    -39    with redirect_stdout(f):
    -40        log_address(('0.0.0.0', 12345), 'test message')
    -41    out = f.getvalue()
    -42    print(out)
    -43    assert out.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
    + 15import pytest
    + 16import io
    + 17from contextlib import redirect_stdout
    + 18
    + 19from myserver.log import log, log_address, log_request, log_reply
    + 20
    + 21
    + 22def test_log():
    + 23    """
    + 24    Tests the log() function by capturing stdout.
    + 25    """
    + 26    f = io.StringIO()
    + 27    with redirect_stdout(f):
    + 28        log('test message')
    + 29    out = f.getvalue()
    + 30    print(out)
    + 31    assert out.endswith(' - test message\n')
    + 32
    + 33    
    + 34def test_log_address():
    + 35    """
    + 36    Tests the log_address() function by capturing stdout.
    + 37    """
    + 38    f = io.StringIO()
    + 39    with redirect_stdout(f):
    + 40        log_address(('0.0.0.0', 12345), 'test message')
    + 41    out = f.getvalue()
    + 42    print(out)
    + 43    assert out.endswith(' - 0.0.0.0:12345 - test message\n')
    + 44
    + 45
    + 46def test_log_request():
    + 47    """
    + 48    Tests the log_request() function by capturing stdout.
    + 49    """
    + 50    req = {
    + 51        'head': {
    + 52            'verb': 'GET',
    + 53            'resource': '/index.html'
    + 54            }
    + 55    }
    + 56    f = io.StringIO()
    + 57    with redirect_stdout(f):
    + 58        log_request(('0.0.0.0', 12345), req)
    + 59    out = f.getvalue()
    + 60    print(out)
    + 61    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html\n')
    + 62
    + 63def test_log_request_useragent():
    + 64    """
    + 65    Tests the log_request() function by capturing stdout.
    + 66    """
    + 67    req = {
    + 68        'head': {
    + 69            'verb': 'GET',
    + 70            'resource': '/index.html'
    + 71        },
    + 72        'params': {
    + 73            'User-Agent': 'MyTest UserAgent v1.23'
    + 74        }
    + 75    }
    + 76    f = io.StringIO()
    + 77    with redirect_stdout(f):
    + 78        log_request(('0.0.0.0', 12345), req)
    + 79    out = f.getvalue()
    + 80    print(out)
    + 81    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html - MyTest UserAgent v1.23\n')
    + 82
    + 83def test_log_request_failed_assert():
    + 84    """
    + 85    Tests if the log_request() function properly fails when wrong
    + 86    parameters are provided.
    + 87    """
    + 88    req = {
    + 89        'head': {
    + 90            'verb': 'GET',
    + 91            }
    + 92    }
    + 93    with pytest.raises(AssertionError) as e_info:
    + 94        log_request(('0.0.0.0', 12345), req)
    + 95    
    + 96    req = {
    + 97        'whatever': {
    + 98            'verb': 'GET',
    + 99            'resource': '/index.html'
    +100            }
    +101    }
    +102    with pytest.raises(AssertionError) as e_info:
    +103        log_request(('0.0.0.0', 12345), req)
    +104    
    +105def test_log_reply():
    +106    """
    +107    Tests the log_reply() function by checking its output.
    +108    """
    +109    req = {
    +110        'head': {
    +111            'verb': 'GET',
    +112            'resource': '/index.html'
    +113            }
    +114    }
    +115    f = io.StringIO()
    +116    with redirect_stdout(f):
    +117        log_reply(('0.0.0.0', 12345), req, 200)
    +118    out = f.getvalue()
    +119    print(out)
    +120    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html - 200\n')
    +121
    +122    # Check a different status code.
    +123    with redirect_stdout(f):
    +124        log_reply(('0.0.0.0', 12345), req, 403)
    +125    out = f.getvalue()
    +126    print(out)
    +127    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html - 403\n')
    +128    
    +129def test_log_reply_useragent():
    +130    """
    +131    Tests the log_reply() function by checking its output.
    +132    """
    +133    req = {
    +134        'head': {
    +135            'verb': 'GET',
    +136            'resource': '/index.html'
    +137            },
    +138        'params': {
    +139            'User-Agent': 'MyTest UserAgent v1.23'
    +140        }
    +141    }
    +142    f = io.StringIO()
    +143    with redirect_stdout(f):
    +144        log_reply(('0.0.0.0', 12345), req, 200)
    +145    out = f.getvalue()
    +146    print(out)
    +147    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html - 200 - MyTest UserAgent v1.23\n')
    +148
    +149    with redirect_stdout(f):
    +150        log_reply(('127.0.0.1', 12345), req, 502)
    +151    out = f.getvalue()
    +152    print(out)
    +153    assert out.endswith(' - 127.0.0.1:12345 - GET /index.html - 502 - MyTest UserAgent v1.23\n')
     
    @@ -163,6 +288,206 @@
    +
    +
    + +
    + + def + test_log_request(): + + + +
    + +
    47def test_log_request():
    +48    """
    +49    Tests the log_request() function by capturing stdout.
    +50    """
    +51    req = {
    +52        'head': {
    +53            'verb': 'GET',
    +54            'resource': '/index.html'
    +55            }
    +56    }
    +57    f = io.StringIO()
    +58    with redirect_stdout(f):
    +59        log_request(('0.0.0.0', 12345), req)
    +60    out = f.getvalue()
    +61    print(out)
    +62    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html\n')
    +
    + + +

    Tests the log_request() function by capturing stdout.

    +
    + + +
    +
    + +
    + + def + test_log_request_useragent(): + + + +
    + +
    64def test_log_request_useragent():
    +65    """
    +66    Tests the log_request() function by capturing stdout.
    +67    """
    +68    req = {
    +69        'head': {
    +70            'verb': 'GET',
    +71            'resource': '/index.html'
    +72        },
    +73        'params': {
    +74            'User-Agent': 'MyTest UserAgent v1.23'
    +75        }
    +76    }
    +77    f = io.StringIO()
    +78    with redirect_stdout(f):
    +79        log_request(('0.0.0.0', 12345), req)
    +80    out = f.getvalue()
    +81    print(out)
    +82    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html - MyTest UserAgent v1.23\n')
    +
    + + +

    Tests the log_request() function by capturing stdout.

    +
    + + +
    +
    + +
    + + def + test_log_request_failed_assert(): + + + +
    + +
     84def test_log_request_failed_assert():
    + 85    """
    + 86    Tests if the log_request() function properly fails when wrong
    + 87    parameters are provided.
    + 88    """
    + 89    req = {
    + 90        'head': {
    + 91            'verb': 'GET',
    + 92            }
    + 93    }
    + 94    with pytest.raises(AssertionError) as e_info:
    + 95        log_request(('0.0.0.0', 12345), req)
    + 96    
    + 97    req = {
    + 98        'whatever': {
    + 99            'verb': 'GET',
    +100            'resource': '/index.html'
    +101            }
    +102    }
    +103    with pytest.raises(AssertionError) as e_info:
    +104        log_request(('0.0.0.0', 12345), req)
    +
    + + +

    Tests if the log_request() function properly fails when wrong +parameters are provided.

    +
    + + +
    +
    + +
    + + def + test_log_reply(): + + + +
    + +
    106def test_log_reply():
    +107    """
    +108    Tests the log_reply() function by checking its output.
    +109    """
    +110    req = {
    +111        'head': {
    +112            'verb': 'GET',
    +113            'resource': '/index.html'
    +114            }
    +115    }
    +116    f = io.StringIO()
    +117    with redirect_stdout(f):
    +118        log_reply(('0.0.0.0', 12345), req, 200)
    +119    out = f.getvalue()
    +120    print(out)
    +121    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html - 200\n')
    +122
    +123    # Check a different status code.
    +124    with redirect_stdout(f):
    +125        log_reply(('0.0.0.0', 12345), req, 403)
    +126    out = f.getvalue()
    +127    print(out)
    +128    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html - 403\n')
    +
    + + +

    Tests the log_reply() function by checking its output.

    +
    + + +
    +
    + +
    + + def + test_log_reply_useragent(): + + + +
    + +
    130def test_log_reply_useragent():
    +131    """
    +132    Tests the log_reply() function by checking its output.
    +133    """
    +134    req = {
    +135        'head': {
    +136            'verb': 'GET',
    +137            'resource': '/index.html'
    +138            },
    +139        'params': {
    +140            'User-Agent': 'MyTest UserAgent v1.23'
    +141        }
    +142    }
    +143    f = io.StringIO()
    +144    with redirect_stdout(f):
    +145        log_reply(('0.0.0.0', 12345), req, 200)
    +146    out = f.getvalue()
    +147    print(out)
    +148    assert out.endswith(' - 0.0.0.0:12345 - GET /index.html - 200 - MyTest UserAgent v1.23\n')
    +149
    +150    with redirect_stdout(f):
    +151        log_reply(('127.0.0.1', 12345), req, 502)
    +152    out = f.getvalue()
    +153    print(out)
    +154    assert out.endswith(' - 127.0.0.1:12345 - GET /index.html - 502 - MyTest UserAgent v1.23\n')
    +
    + + +

    Tests the log_reply() function by checking its output.

    +
    + +