diff --git a/Changes.txt b/Changes.txt index 58494e71..f43052c5 100644 --- a/Changes.txt +++ b/Changes.txt @@ -9,6 +9,138 @@ BEGIN CHANGELOG: __________________________________________________________________________________________________________________________________________________________________ (XX.XX.XXXX) commit (latest, unknown hash) ? files changed with ? additions and ? deletions. __________________________________________________________________________________________________________________________________________________________________ +(27.06.2021) commit (latest, unknown hash) 8 files changed with ? additions and ? deletions. + + Add binary handlers in DbApiHandler.cs + +Short changes: + Add supporting of binary requests, and binary-responses. + Add binary handlers in _handlers_bin dictionary, in DbApiHandler.cs + This handlers assept byte[] from binary-requests, sent with "Content-Type: application/octet-stream". + A text-handlers in _handlers dictionary, accept strings only. + + The reversive encoding "iso-8859-1" (latin1), can encode each one byte into each one char in range "\u0000-\u00FF", + so this encoding can be used, to send bytes, as string on the text-handlers. + But, by default, there is an utf-8 bytes, so this decoded as UTF-8 text. + + Add an example text-handlers + (text-handler) "echo-string" + accept utf-8 encoded string, and echo this as string. + this can accept AllBytes as latin-1 string, but return this as utf-8 encoded string. + + (text-handler) "echo-binary-string" + accept AllBytes, as latin1-encoded string, + and echo this as binary-response (with "Content-Type: application/octet-stream") + + (binary-handler) "echo-bytes" + accept AllBytes, as binary request (with "Content-Type: application/octet-stream") + and echo this as binary-response (with "Content-Type: application/octet-stream") + + Add test file: \pages\test_echo_handlers.html + 44 tests, with example code, + how to send the data with different types, + and how to decode the responses back. + +Full changes: + \nanodb.exe-source\Server\DbApiHandler.cs + Lines 28 : Add Dictionary> _handlers_bin + Lines 34 : Set this as new Dictionary>(); + Lines 96 - 99 : Att three test-handlers for full-server: + "echo-string" //return the same string, utf-8 + "echo-binary-string" //return bytes of binary string + "echo-bytes" //return bytes of binary request + Lines 170 - 173 : Add the same handlers for lite-server, but comment this, to prevent DDoS-attack, with large requests. + Lines 174 - 181 : Rewrite long line with code. + Lines 183 - 187 : Add private HttpResponse EchoString to echo text-string in utf-8 encoding. + Lines 189 - 204 : Add private HttpResponse EchoBinaryString to echo text-string in iso-8859-1 (latin1) encoding. + Lines 206 - 214 : Add private HttpResponse EchoBytes to echo bytes, that was been send with "Content-Type: application/octet-stream" + Lines 216 - 232 : Add public static bool isLatin1String + commented javascript-line. Use _lockObj there. + Lines 1136 : Made this _lockObj the private static, because isLatin1String is public static. + Lines 1268 : Add comment for HttpResponse Handle + Lines 1269-1213 : Rewrite HttpResponse Handle, and add supporting of binary-handlers: "_handlers_bin", that accept byte[], not string. + + \nanodb.exe-source\Server\HttpConnection.cs + Lines 20 : Rename string Request to string RequestText, because this is a text of request. + Lines 21 : Rename byte[] Raw to byte[] RequestBytes, because this is a bytes for binary-requests. + Lines 24 : string RequestHeadersString, to save headers from TcpServer.cs + Lines 25 : Add bool isBinaryRequest = false + Lines 27 - 34 : Rewrite arguments one per line. + Add two optional arguments: string headers = null, and bool set_binary_request = false + Lines 36 : Rename Raw to RequestBytes, because this is a bytes of binary-requests. + Lines 37 : Rename Request to RequestText, because this is a text of text-request. + Lines 40 - 44 : Set "RequestHeadersString" and "isBinaryRequest" from optional arguments, if this was been specified. + Lines 53 : Change condition from (response.IsBinary) to (response.IsBinary == true), to show this must to be true. + Lines 54, 56 : Using GetResponseHeaders() instead of GetResponse(); because this is a headers. + + \nanodb.exe-source\Server\HttpRequest.cs + Lines 26 : Add public readonly bool isBinary = false; + to mark binary requests. + Lines 27 : Add public readonly byte[] Content_bytes; + to save bytes of binary-request. + Lines 28 : Add public readonly int MaxHeadersSize = 48*1024; + to read partial request with headers, from large POST-request. + Lines 29 : Add public readonly int MaxBinaryContentStringSize = 4*1024*1024; + to limit size of binary string, that can be writted from binary-request. + Lines 31 : Add private readonly object _lockObj + to lock process + Lines 33 - 49 : Move the code to extract headers from "he", into separate method: + public Dictionary GetHeadersFromString(ref string he). + Use lock, there. + Lines 51 : Rewrite public HttpRequest(HttpConnection conn), use conn only, there. + Lines 53 : Use lock, in public HttpRequest(HttpConnection conn) + Lines 55 - 56 : Add two lines, with comments, about state of text-request, and binary-request. + Lines 57 - 60 : Extract four values from conn, add commented default values of this, and comments. + Lines 62 - 210 : Rewrite the rest code. + Add supporting of binary-reqests. + Write Content_bytes from bytes for binary-requests, and leave Content = null. + use headers, parsed in TcpServer.cs, but do extracting the headers optionally, + if this was been not specified, in conn.HeadersString + Lines 212 : use method: Headers = GetHeadersFromString(ref he); //get Dictionary Headers from "he" + + \nanodb.exe-source\Server\HttpServer.cs + Lines 52 : OnConnectionAdded: Remove old test-code with Console.WriteLine, + and use connection only, in arguments of running HttpRequest. + Lines 63 : OnConnectionAdded_liet: the same change. + Lines 84 : Add line to show request.Headers["Referrer"], + but leave this commented. + Lines 139 : Rename connection.Request to connection.RequestText, because this was been renamed in HttpConnection.cs + + \nanodb.exe-source\Server\HttpResponse.cs + Lines 15 : Rename private readonly string _response; to private readonly string _headers; + because really, this is a headers of response. + Lines 18 : Add private readonly bool _isBinary; + to set this flag true, when response is binary. + Lines 28 - 43 : Rewrite HttpResponse for binary responses, that accept "byte[] bytes" + Lines 45 - 75 : Rewrite HttpResponse for text-responses, that accept "string content" + Add optional parameters, there. + Now this can return bytes, if isBinaryResponse and encoding was been specified. + Lines 77 - 82 : Rewrite short version of HttpResponse(string code, string content) + Add there an optional parameters bool isBinaryResponse = false, System.Text.Encoding encoding = null + Now this return binary responses, as bytes of text, encoded with specified encoding. + Lines 84 : Rename GetResponse() to GetResponseHeaders(), because really this return headers. + Lines 86 : Rename _response to _headers, because really, this is a headers of response. + + \nanodb.exe-source\Server\TcpServer.cs + Lines 555 : Comment test Console.WriteLine. + Lines 557 : Comment test Console.WriteLine. + Lines 561 : Add comment + Lines 824 - 827 : Add two test Console.WriteLine, but comment this. + Lines 861 : Add optional argument "headers", readed in "headers"-variable, to save this in HttpConnection.HeadersString + Lines 862 : Add test value of null, there, to test the case when headers not specified. + HttpRequest, must to parse this from if this not exists in conn.HeadersString. + Lines 864 - 865 : Add two comments about state of requests, when "is_binary_request" not specified. + Lines 866 : Add optional argument "is_binary_request" that got value in TcpServer.cs + Lines 867 : Add commented the test case, "true", and "false", when this value specified for any request (seems, like ok, in both cases. + + \pages\test_echo_handlers.html + Add this file with 44 tests. + This available on http://IP:PORT/pages/test_echo_handlers.html + See source code and comments. + Make this file readonly, to prevent modifications of the source code. + This is just an example of JavaScript code, to send the different types of data on the API Handlers of DbApiHandler.cs. + + \Changes.txt - Update this file "Changes.txt" +__________________________________________________________________________________________________________________________________________________________________ (23.06.2021) commit (latest, unknown hash) 2 files changed with ? additions and ? deletions. Rewrite TcpServer.cs diff --git a/nanodb.exe-source/Server/DbApiHandler.cs b/nanodb.exe-source/Server/DbApiHandler.cs index f53962d3..c97f7632 100644 --- a/nanodb.exe-source/Server/DbApiHandler.cs +++ b/nanodb.exe-source/Server/DbApiHandler.cs @@ -25,11 +25,13 @@ class DbApiHandler : IRequestHandler { private PostDb _db; private Dictionary> _handlers; + private Dictionary> _handlers_bin; private bool is_lite = false; //True, when this is request on lite-server, or false, if this is request on full-server. public DbApiHandler(PostDb db, bool lite = false, bool SetAllowReput = false, bool SetByPassValidation = false) { _db = db; _handlers = new Dictionary>(); + _handlers_bin = new Dictionary>(); is_lite = lite; allowReput = SetAllowReput; bypassValidation = SetByPassValidation; //set this once, for current Instance. if(lite == false){ //all actions allowed for full server // filling handlers dictionary with actions that will be called with (request address, request content) args: @@ -91,6 +93,10 @@ public DbApiHandler(PostDb db, bool lite = false, bool SetAllowReput = false, bo _handlers["delete_reports"] = DeleteReportsForPostHash; _handlers["undelete_post"] = UndeletePost; //undelete post, after this was been "deleted_once" on full-server: http://127.0.0.1:7346/api/undelete_post/{POST_HASH} _handlers["random_captcha"] = RandomCaptcha; //GET random captcha or solve this (POST) + + _handlers ["echo-string"] = EchoString; //return the same string, utf-8 + _handlers ["echo-binary-string"] = EchoBinaryString; //return bytes of binary string + _handlers_bin ["echo-bytes"] = EchoBytes; //return bytes back } else if(lite == true){ //not all actions allowed for lite-server to run this by another anonymous users. @@ -161,9 +167,70 @@ public DbApiHandler(PostDb db, bool lite = false, bool SetAllowReput = false, bo // _handlers["png-collect-avail"] = (a,b)=>new HttpResponse(_collectAvail ? StatusCode.Ok : StatusCode.NotFound, _collectAvail ? "Finished." : "Collect..."); _handlers["png-create-avail"] = (a,b)=>new HttpResponse(_createAvail ? StatusCode.Ok : StatusCode.NotFound, _createAvail ? "Finished." : "Creating PNG..."); _handlers["random_captcha"] = RandomCaptcha; //GET random captcha or solve this (POST) - } Set_Timer_To_Delete_Generated_Images(); - }private static bool allowReput = false; private static bool bypassValidation = false; /*//true if need to make bypassValidation for posts in containers, while png-collect is processing.*/ + +// _handlers ["echo-string"] = EchoString; //return the same string, utf-8 +// _handlers ["echo-binary-string"] = EchoBinaryString; //return bytes of binary string encoded with iso-8859-1 +// _handlers_bin ["echo-bytes"] = EchoBytes; //return bytes back + } + + Set_Timer_To_Delete_Generated_Images(); + } + + private static bool allowReput = false; + private static bool bypassValidation = false; + /*//true if need to make bypassValidation for posts in containers, while png-collect is processing.*/ + + //echo string, in any encoding. + private HttpResponse EchoString(string stringGET = null, string stringPOST = null){ + string data = param_GET_POST(stringGET, stringPOST); + return new HttpResponse(StatusCode.Ok, data); + } + + //echo binary string, as binary response + private HttpResponse EchoBinaryString(string stringGET = null, string stringPOST = null){ + string data = param_GET_POST(stringGET, stringPOST); + //Console.WriteLine("EchoBinaryString: data.Length: "+data.Length); + + System.Text.Encoding encoding = null; + if(isLatin1String(data)){ + encoding = System.Text.Encoding.GetEncoding("iso-8859-1"); + } + else{ + encoding = System.Text.Encoding.UTF8; + } + + return new HttpResponse(StatusCode.Ok, data, MimeType.Mime("App", encoding), true, encoding); //just return back, the binary string with bytes, in HTTP response. + //"true", means response is binary, even if this is a string. + } + + //echo bytes from binary request. + private HttpResponse EchoBytes(string path = null, byte[] bytes = null){ + try{ + return new HttpResponse(StatusCode.Ok, bytes, MimeType.Mime("App")); //just return binary data in HTTP response. + }catch(Exception ex){ + Console.WriteLine(ex); + return new HttpResponse(StatusCode.Ok, ex.Message, MimeType.Mime("Html")); //just return binary data in HTTP response. + } + } + //C#: check is string "iso-8859-1"-encoded or not (true/false) + public static bool isLatin1String(string str){ + lock (_lockObj) + { + return ( + Regex.Match( + str //for each char in str + , @"[^\u0000-\u00FF]" //try to match not latin1-char, and stop + , RegexOptions.None + ) + .Length //then take Length of result + ) == 0 //and compare this to 0 + ; //true/false (non-latin character was not found or was found) + } + } + // JavaScript: + //function isLatin1String(str){return (str.match(/[^\u0000-\u00FF]/) === null);} //check is string "iso-8859-1"-encoded or not (true/false) + /* This method return string with parameters, from request parameters. It return first or second parameter of request query (if second is available). @@ -1066,7 +1133,7 @@ private HttpResponse ReAddPost(string replyTo, string content) return new HttpResponse(StatusCode.Ok, JsonConvert.SerializeObject(post)); } - private object _lockObj = new object(); + private static readonly object _lockObj = new object(); //Delete post "delete_once", or "delete_forever", if post was already "deleted_once". private HttpResponse DeletePost(string hash, string notUsed = null) @@ -1198,6 +1265,7 @@ private HttpResponse UndeletePost(string hash, string notUsed = null) ); } + //Handle API-request, and return HttpResponse. public HttpResponse Handle(HttpRequest request) { try @@ -1206,20 +1274,41 @@ public HttpResponse Handle(HttpRequest request) var cmd = splitted.Length < 2 ? "" : splitted[1]; var arg = splitted.Length < 3 ? "" : splitted[2]; - if (_handlers.ContainsKey(cmd)) - { - return _handlers[cmd](arg, request.Content); + if( //if + _handlers.ContainsKey(cmd) //handler contains in _handlers Dictionary + // && request.isBinary == false //and if it's not binary-request + //this was been commented, because binary requests can be accepted by handlers, as iso-8859-1 string, too. + ){ + return _handlers[cmd](arg, request.Content); //send request to handler, and return response. + //for binary-request, send request to "handler" with "iso-8859-1"-encoded string, and return response. } - else + else if ( //else if + _handlers_bin.ContainsKey(cmd) //and if handler contains in binary handlers + ) { - return new ErrorHandler(StatusCode.BadRequest, "No such command: " + cmd + ". Available commands: " + JsonConvert.SerializeObject(_handlers.Keys.ToArray())).Handle(request); + if( //if + request.Content_bytes == null //but content not found + || ( //or + (request.Content_bytes != null) //if it's found + && (request.Content_bytes.Length == 0) //but empty + ) + && request.Content.Length != 0 //and if text found + ){ + // Console.WriteLine("request.Content"+request.Content); //it seems, like "iso-8859-1"-text sent, as string. + byte[] request_bytes = MimeType.DefaultBinaryEncoding.GetBytes(request.Content); //decode this to bytes, using reversive latin1 encoding. + return _handlers_bin[cmd](arg, request_bytes); //and send bytes to binary-handler, and return the binary response + } + else{ //else if it's was been bytes (Content-Type: application/octet-stream), and request.isBinary == true + return _handlers_bin[cmd](arg, request.Content_bytes); //send bytes to binary-handler, and return response + } } + //else if "_handlers" or "_handlers_bin" dictionaries not contains handler "cmd" - return an error: + return new ErrorHandler(StatusCode.BadRequest, "No such command: " + cmd + ". Available commands: " + JsonConvert.SerializeObject(_handlers.Keys.ToArray())).Handle(request); } - catch (Exception e) { - return new ErrorHandler(StatusCode.InternalServerError, e.Message).Handle(request); + return new ErrorHandler(StatusCode.InternalServerError, e.Message).Handle(request); //or return an exception-message } } } diff --git a/nanodb.exe-source/Server/HttpConnection.cs b/nanodb.exe-source/Server/HttpConnection.cs index 84a579a3..462601d2 100644 --- a/nanodb.exe-source/Server/HttpConnection.cs +++ b/nanodb.exe-source/Server/HttpConnection.cs @@ -17,19 +17,31 @@ response callbacks */ class HttpConnection { - public readonly string Request; + public readonly string RequestText; //request-text + public readonly byte[] RequestBytes; //requestBytes (if binary-request) private Action _callback; private Action _binaryCallback; - public readonly byte[] Raw; + public readonly string RequestHeadersString = null; //headers, as string, without '\r\n\r\n', in the end + public readonly bool isBinaryRequest = false; //true, when request is binary - public HttpConnection(byte[] raw, string request, - Action asciiUtf8callback, - Action asciiBytesCallback = null) + public HttpConnection( + byte[] raw + , string request + , Action asciiUtf8callback + , Action asciiBytesCallback = null + , string headers = null + , bool set_binary_request = false + ) { - this.Raw = raw; - this.Request = request; - this._callback = asciiUtf8callback; - this._binaryCallback = asciiBytesCallback; + this.RequestBytes = raw; + this.RequestText = request; + this._callback = asciiUtf8callback; + this._binaryCallback = asciiBytesCallback; + + this.RequestHeadersString = headers; + //true, when specified, or when (bytes exists, but request-text not exists). + this.isBinaryRequest = ( ( set_binary_request == true ) || ( (raw != null) && (request == null) ) ); + //Console.WriteLine("HttpConnection.cs. isBinaryRequest: "+isBinaryRequest); } public void Response(HttpResponse response) @@ -38,10 +50,10 @@ public void Response(HttpResponse response) Console.WriteLine("HttpConnection.cs. response is null, return..."); return; } - if (response.IsBinary){ - _binaryCallback(response.GetResponse(), response.GetBinaryContent()); + if (response.IsBinary == true){ + _binaryCallback(response.GetResponseHeaders(), response.GetBinaryContent()); }else{ - _callback(response.GetResponse(), response.GetContent()); + _callback(response.GetResponseHeaders(), response.GetContent()); } } } diff --git a/nanodb.exe-source/Server/HttpRequest.cs b/nanodb.exe-source/Server/HttpRequest.cs index 90d175a3..a995a0ef 100644 --- a/nanodb.exe-source/Server/HttpRequest.cs +++ b/nanodb.exe-source/Server/HttpRequest.cs @@ -23,47 +23,194 @@ class HttpRequest public readonly HttpConnection Connection; public readonly bool Invalid = false; - public HttpRequest(HttpConnection conn, string request) - { - Connection = conn; - if (string.IsNullOrEmpty(request)) - { - Invalid = true; - return; - } + public readonly bool isBinary = false; //true, when request is binary + public readonly byte[] Content_bytes; //bytes of binary-request + public readonly int MaxHeadersSize = 48*1024; //48 KiloBytes; + public readonly int MaxBinaryContentStringSize = 4*1024*1024; //4MegaBytes + + private readonly object _lockObj = new object(); + + //extract the headers from string "he", and extract this - into "Dictionary Headers", and return this. + public Dictionary GetHeadersFromString(ref string he){ + lock(_lockObj) + { + Dictionary ExtractedHeaders = new Dictionary(); + var lines = he.Split(new string[]{ "\r\n", "\n" }, StringSplitOptions.None); - var fls = request.Split(new char[]{ ' ' }, 4); + for (int i = 1; i < lines.Length; i++) + { + var hl = lines[i].Split(new String[]{": "}, 2, StringSplitOptions.None); + if (hl.Length < 2) continue; + ExtractedHeaders[hl[0]] = hl[1]; + } +// Console.WriteLine("Method: "+Method+", Address: "+Address+", Host: "+Host+", Content: "+Content+", Invalid: "+Invalid ); + return ExtractedHeaders; + } + } + + public HttpRequest(HttpConnection conn) //HttpConnection + { + lock(_lockObj) + { + //text request contains "HeadersString", and "request". ("requestBytes" == null) True; + //binary request contains "HeadersString", and "requestBytes". ("request" == null) True; + string HeadersString = conn.RequestHeadersString; //= null ; //headers, as string, without '\r\n\r\n', in the end + string RequestText = conn.RequestText; //= null ; //request-text + byte[] RequestBytes = conn.RequestBytes; //= null ; //requestBytes (if binary-request) + bool isBinaryRequest = conn.isBinaryRequest; //= false ; //true, when request is binary + + Connection = conn; //HttpConnection conn + if (string.IsNullOrEmpty(RequestText)) //if RequestText is empty + { + if(RequestBytes != null) //but if requestBytes not empty + { + //This is a binary-request + isBinary = ( isBinaryRequest || (RequestBytes != null) && (RequestText == null) ); //true, when request is binary + //Console.WriteLine("HttpRequest: isBinary: "+isBinary); + + if(string.IsNullOrEmpty(HeadersString)){ //if headers not specified + //extract this from RequestBytes of binary request. - if (fls.Length < 3) - { - Invalid = true; - return; - } + //define bytearray with request or partial request (when this too large) + byte[] HeadersContainsHere = new byte[ ( ( RequestBytes.Length > MaxHeadersSize ) ? MaxHeadersSize : RequestBytes.Length )]; + //and write the bytes with headers, there + System.Buffer.BlockCopy(RequestBytes, 0, HeadersContainsHere, 0, HeadersContainsHere.Length ); + + //then, get partial request-string, with headers, from bytes, and get it as "iso-8859-1"-encoded string. + RequestText = MimeType.DefaultBinaryEncoding //this encoding must to be reversive, because bytes are encoded + .GetString(HeadersContainsHere) + ; + //and continue with this. + }else{ //else, if specified + //Just continue with this. + RequestText = HeadersString+"\r\n\r\n"; + } + } + else if(RequestBytes == null || RequestBytes.Length == 0){ + //else if both "RequestText", and "RequestBytes" are empty - invalid request. + Invalid = true; + Console.WriteLine( + "HttpRequest.cs. ( (string.IsNullOrEmpty(RequestText)) && (RequestBytes != null) )" + + ( (string.IsNullOrEmpty(RequestText)) && (RequestBytes != null) ) + + "\n" + "Invalid Request" + ); + return; + } + } - Method = fls[0]; - Address = fls[1]; - Host = ((fls[3]).Split(new string[]{ "\r\n" }, 2, StringSplitOptions.None))[0]; + var fls = RequestText.Split(new char[]{ ' ' }, 4); - var heco = request.Split(new string[]{ "\r\n\r\n" }, 2, StringSplitOptions.None); + if (fls.Length < 3) + { + Invalid = true; + return; + } - if (heco.Length < 2) //DON'T CHANGE THIS VALUE, because params in requests will not be transferred correctly. - { - Invalid = true; - return; - } + Method = fls[0]; + Address = fls[1]; + Host = ((fls[3]).Split(new string[]{ "\r\n" }, 2, StringSplitOptions.None))[0]; - var he = heco[0]; - var co = heco[1]; - Content = co; - var lines = he.Split(new string[]{ "\r\n", "\n" }, StringSplitOptions.None); + string[] heco ; //Headers and Content + string he ; //Headers - for (int i = 1; i < lines.Length; i++) - { - var hl = lines[i].Split(new String[]{": "}, 2, StringSplitOptions.None); - if (hl.Length < 2) continue; - Headers[hl[0]] = hl[1]; - } -// Console.WriteLine("Method: "+Method+", Address: "+Address+", Host: "+Host+", Content: "+Content+", Invalid: "+Invalid ); + if(string.IsNullOrEmpty(HeadersString)) //if "HeadersString" not specified + { + heco = RequestText.Split(new string[]{ "\r\n\r\n" }, 2, StringSplitOptions.None); //extract this from RequestText + if (heco.Length < 2) //if content not found + { + Invalid = true; + Console.WriteLine("Content not found, invalid request..."); + return; + } + he = heco[0]; //set headers in "he" + } + else{ //else if "HeadersString" specified + heco = new string[]{HeadersString, "" }; //use specified "HeadersString" and empty content + if(isBinary == false) //if this was been a text-request + { + heco[1] = RequestText.Substring(HeadersString.Length+4); //use "RequestText", but without headers + } + he = HeadersString; //and set "HeadersString" in "he" + } + if(isBinary == false){ //if Text-request + string co = null; //set this null + if( + ( + (RequestBytes == null) //if bytes not exists + || ( + (RequestBytes != null) //or if exists + && (RequestBytes.Length == 0) //but empty bytearray + ) + ) + || //or if + ( + (RequestBytes.Length > 0) //RequestBytes exists + && !string.IsNullOrEmpty(heco[1]) //and some content exists too + ) + ){ + co = heco[1]; //extract content here even if heco[1] is empty. + } + else if( + (RequestBytes.Length > 0) //and not empty + && string.IsNullOrEmpty(heco[1]) //but content not found, after split "RequestText" + ){ + co = MimeType.DefaultBinaryEncoding.GetString(RequestBytes).Substring(he.Length+4); //get latin1-string from "RequestBytes", without headers + //no need to write ContentBytes, because Text-requests on _handlers_bin converts into bytes, in the Handle-method of DbApiHandler.cs + } + else{ //else + //show this + Console.WriteLine("HttpRequest.cs: Something wrong with condition... Invalid request..."); + Invalid = true; + return; + } + //else, when "co" defined, set this in "Content". + Content = co; //and use this + //and continue to parse Headers... + } + else{ //else if binary request + if( + RequestBytes != null //RequestBytes exists + && RequestBytes.Length != 0 //and this is not empty + // && RequestText == null //and if RequestText not exists + //or if "RequestText" exists too (previous line was been commented) + ){ + //"Content_bytes" contains the bytes of binary-content, without headers + int HeadersWithCRLFCRLF = (he.Length+4); //length of headers with "\r\n\r\n", in the end (CR, LF, CR, LF) = 4 bytes + Content_bytes = new byte[RequestBytes.Length - HeadersWithCRLFCRLF]; + System.Buffer.BlockCopy(RequestBytes, HeadersWithCRLFCRLF, Content_bytes, 0, Content_bytes.Length ); + + //And if binary content "Content_bytes" is not so large + if(Content_bytes.Length < MaxBinaryContentStringSize){ + //leave "Content", as "Content_bytes" latin1-encoded string + Content = MimeType.DefaultBinaryEncoding.GetString(Content_bytes); //Use reversive "iso-8859-1"-encoding + }else{ + //else, show this. + Console.WriteLine("HttpRequest. \"Content_bytes\" too large to be decoded into latin1-string. Leave bytes only."); + } + } + else if( + ( + RequestBytes == null + || RequestBytes.Length == 0 + ) + && RequestText != null + ) + { + var co = heco[1]; //extract content + Content_bytes = MimeType.DefaultTextEncoding.GetBytes(co); //put UTF8-bytes of Text-Content - into "Content_bytes" + isBinary = true; //and set it "true" + } + else{ + Console.WriteLine("(RequestBytes == null)"+(RequestBytes == null)); + Console.WriteLine("(RequestText == null)"+(RequestText == null)); + Invalid = true; + Console.WriteLine("HttpRequest.cs: Invalid binary request..."); + return; + } + } + + Headers = GetHeadersFromString(ref he); //get Dictionary Headers from "he" + } } } } diff --git a/nanodb.exe-source/Server/HttpResponse.cs b/nanodb.exe-source/Server/HttpResponse.cs index 6b22c61a..c2edb9e2 100644 --- a/nanodb.exe-source/Server/HttpResponse.cs +++ b/nanodb.exe-source/Server/HttpResponse.cs @@ -12,9 +12,10 @@ namespace NServer { class HttpResponse { - private readonly string _response; - private readonly string _content; - private readonly byte[] _bytes; + private readonly string _headers; + private readonly string _content; + private readonly byte[] _bytes; + private readonly bool _isBinary; public bool IsBinary { @@ -24,28 +25,65 @@ public bool IsBinary } } - public HttpResponse(string code, byte[] bytes, string contentType) + public HttpResponse( + string code + , byte[] bytes + , string contentType + , string filename = null + ) { - _response = "HTTP/1.1 " + code + "\r\nContent-type: " + contentType + (bytes == null ? "\r\n" : ("\r\n\r\n")); + _headers = "HTTP/1.1 " + + code + + ("\r\nContent-type: " + contentType) + + ((filename != null)?("\r\nContent-Disposition: attachment; filename=\""+(filename)+"\"\r\n"):"") //return file, with filename. + + (bytes == null ? "\r\n" : ("\r\n\r\n")) + ; _bytes = bytes; + _isBinary = IsBinary; } - - public HttpResponse(string code, string content, string contentType) + + public HttpResponse( + string code + , string content + , string contentType + , bool isBinaryResponse = false + , System.Text.Encoding encoding = null //specify this, or UTF-8, to encode text-"_content" into "_bytes". + ) { - _response = "HTTP/1.1 " + code + "\r\nContent-type: " + contentType + (string.IsNullOrEmpty(content) ? "\r\n" : ("\r\n\r\n")); + encoding = MimeType.UseEnc(encoding); //use specified or default encoding. + + _headers = + "HTTP/1.1 " + + code + "\r\n" + + "Content-type: " + contentType + + (string.IsNullOrEmpty(content) ? "\r\n" : ("\r\n\r\n")) + ; _content = content; + + if(isBinaryResponse == true) + { + if( + _bytes == null + && _content.Length != 0 + ) + { + //Console.WriteLine("HttpResponse. Using "+encoding+" to encode text-response to bytes of binary response."); + _bytes = encoding.GetBytes(_content); + } + } + _isBinary = IsBinary; } - public HttpResponse(string code, string content) - : this(code, content, MimeType.Mime("Plain")) + public HttpResponse(string code, string content, bool isBinaryResponse = false, System.Text.Encoding encoding = null) + : this( code, content, MimeType.Mime("Plain", ( ( encoding == null ) ? null : encoding ) ), isBinaryResponse, encoding ) { - //_response = "HTTP/1.1 " + code + (string.IsNullOrEmpty(content) ? "\r\n" : ("\r\n\r\n")); + //_headers = "HTTP/1.1 " + code + (string.IsNullOrEmpty(content) ? "\r\n" : ("\r\n\r\n")); //_content = content; } - public string GetResponse() + public string GetResponseHeaders() { - return _response; + return _headers; } public string GetContent() diff --git a/nanodb.exe-source/Server/HttpServer.cs b/nanodb.exe-source/Server/HttpServer.cs index 3bf66bfb..0df5547d 100644 --- a/nanodb.exe-source/Server/HttpServer.cs +++ b/nanodb.exe-source/Server/HttpServer.cs @@ -49,8 +49,7 @@ public void Run() private void OnConnectionAdded(HttpConnection connection) { -// Console.WriteLine("HttpServer.cs. OnConnectionAdded. connection.Request.Length: "+connection.Request.Length); - var request = new HttpRequest(connection, connection.Request); + var request = new HttpRequest(connection); if (request.Method == "GET" || request.Method == "POST"){ Process(connection, request, false); } @@ -61,8 +60,7 @@ private void OnConnectionAdded(HttpConnection connection) private void OnConnectionAdded_lite(HttpConnection connection) { -// Console.WriteLine("HttpServer.cs. OnConnectionAdded_lite. connection.Request.Length: "+connection.Request.Length); - var request = new HttpRequest(connection, connection.Request); + var request = new HttpRequest(connection); if (request.Method == "GET" || request.Method == "POST"){ Process(connection, request, true); } @@ -83,6 +81,7 @@ private void Process(HttpConnection connection, HttpRequest request, bool is_lit // Console.WriteLine( // ((is_lite_server)?"Lite":"Full")+", "+DateTime.Now+ //show server, show time, // ": http://"+( ( ((CheckHost).Match(request.Host).Success) == false ) ? request.Headers["Host"] : request.Host )+request.Address //and just show this request. +// +( (request.Headers.ContainsKey("Referrer")) ? " request.Headers[\"Referrer\"]: "+request.Headers["Referrer"] : "") // ); // // //Console.Write("\""+request.Address+"\", "); @@ -137,7 +136,7 @@ private void Process(HttpConnection connection, HttpRequest request, bool is_lit using (StreamWriter sw = File.AppendText("shutdown_requests.log")) { sw.WriteLine(is_lite_server+", "+DateTime.Now+": http://"+( ( ((CheckHost).Match(request.Host).Success) == false ) ? request.Headers["Host"] : request.Host )+request.Address); - sw.WriteLine("connection.Request: "+connection.Request); + sw.WriteLine("connection.RequestText: "+connection.RequestText); sw.WriteLine("\n"); } connection.Response(new ErrorHandler(StatusCode.Ok, "Server was shutdown.").Handle(request)); diff --git a/nanodb.exe-source/Server/TcpServer.cs b/nanodb.exe-source/Server/TcpServer.cs index dc25ce85..58270d8b 100644 --- a/nanodb.exe-source/Server/TcpServer.cs +++ b/nanodb.exe-source/Server/TcpServer.cs @@ -424,51 +424,51 @@ UploadPosts from JSON in cached file. } private bool Write_bytes_in_TCPSocket_block_by_block(NetworkStream stream, byte[] bu){ - //The MSDN documentation says the default size of the send and receive buffers for TcpClient is 8192 bytes, or 8K. - //The documentation does not specify a limit as to how big these buffers can be. - int write_block_size = 8192; //8 KB block //lagging without async-write + //The MSDN documentation says the default size of the send and receive buffers for TcpClient is 8192 bytes, or 8K. + //The documentation does not specify a limit as to how big these buffers can be. + int write_block_size = 8192; //8 KB block //lagging without async-write - int blocks = bu.Length / write_block_size; - int rest_length = bu.Length % write_block_size; + int blocks = bu.Length / write_block_size; + int rest_length = bu.Length % write_block_size; - for(int block_num = 0; (block_num * write_block_size)<=bu.Length; block_num += 1){ - while( - !stream.CanWrite - || data_still_writting == true - || Network_stream_writting_error == true - ){ //if cann't write to the stream, or if data still writting - Thread.Sleep(1); //just sleep 1 millisecond - } - lock(_lock){ - try{ - data_still_writting = true; //set this true -// Console.WriteLine((DateTime.Now.ToString("HH:mm:ss.ffffff"))+", still_writting..."); + for(int block_num = 0; (block_num * write_block_size)<=bu.Length; block_num += 1){ + while( + !stream.CanWrite + || data_still_writting == true + || Network_stream_writting_error == true + ){ //if cann't write to the stream, or if data still writting + Thread.Sleep(1); //just sleep 1 millisecond + } + lock(_lock){ + try{ + data_still_writting = true; //set this true +// Console.WriteLine((DateTime.Now.ToString("HH:mm:ss.ffffff"))+", still_writting..."); //BeginWrite(Byte[], Int32, Int32, AsyncCallback, Object) //Begins an asynchronous write to a stream. - stream.BeginWrite( - bu, //buffer - (block_num * write_block_size), //start offset in buffer - ( ( block_num == blocks ) ? rest_length : write_block_size), //data length to write - new AsyncCallback(async_callback), //callback - stream //NetworkStream - ); //NOW OK. + stream.BeginWrite( + bu, //buffer + (block_num * write_block_size), //start offset in buffer + ( ( block_num == blocks ) ? rest_length : write_block_size), //data length to write + new AsyncCallback(async_callback), //callback + stream //NetworkStream + ); //NOW OK. } catch //(Exception ex) //get exception to show it - { - data_still_writting = false; - Network_stream_writting_error = true; - return false; - } - } - //Console.WriteLine("unlock..."); - } + { + data_still_writting = false; + Network_stream_writting_error = true; + return false; + } + } +// Console.WriteLine("unlock..."); + } data_still_writting = false; Network_stream_writting_error = false; - return true; + return true; } - + private void TryToApplyTcpDelay( ref bool noTcpDelay , ref List raw @@ -505,7 +505,7 @@ ref byte[] block } } } - + private void TryToExtractHeaderFromRequest( ref bool isHeadersReading , ref int block_size @@ -539,7 +539,7 @@ ref bool isHeadersReading else{ isHeadersReading = false; } - } + } //When headers already reading //Check is binary or or text request @@ -552,13 +552,13 @@ ref bool isHeadersReading readData = null; } else{ - Console.WriteLine("TcpServer.cs. Was been: readData: "+readData); + // Console.WriteLine("TcpServer.cs. Was been: readData: "+readData); readData = MimeType.DefaultTextEncoding.GetString(raw.ToArray()); //change encoding to UTF-8 - Console.WriteLine("TcpServer.cs. after change encoding, readData: "+readData); + // Console.WriteLine("TcpServer.cs. after change encoding, readData: "+readData); raw = null; } - enc = (is_binary_request == true) + enc = (is_binary_request == true) //and set the current encoding ? MimeType.DefaultBinaryEncoding : MimeType.DefaultTextEncoding ; @@ -792,7 +792,7 @@ private void HandleClient(TcpClient client, bool is_lite_server) //HandleClient catch //(Exception ex) { //Console.WriteLine("TcpServer.cs, HandleClient, do-while, catch: "+ex); - + TryToApplyTcpDelay(ref noTcpDelay, ref raw); //wait if need TryToExtractHeaderFromRequest( ref isHeadersReading, ref block_size, ref MaxHeadersSize, ref readed_blocks, ref headers, ref readData, ref enc, ref is_binary_request, ref raw); @@ -821,7 +821,11 @@ private void HandleClient(TcpClient client, bool is_lite_server) //HandleClient TryToUploadIntoDataBaseThePostsFromLoadedRequestWithJSON(ref posts_uploading_large_data, ref readData, ref raw, ref headers, ref accepted_posts, ref uploaded_posts, ref too_large); - //after this all... + + //Console.WriteLine("readData.Length: "+ ( (readData != null) ? readData.Length.ToString() : "null" ) ); + //Console.WriteLine("raw.Count: "+ ( (raw != null) ? raw.Count.ToString() : "null" )); + + //after this all... try { byte[] ReadRawBytes = ( (raw != null) ? raw.ToArray() : null); @@ -854,7 +858,14 @@ private void HandleClient(TcpClient client, bool is_lite_server) //HandleClient WriteBytesOfResponse(ref stream, ref ba, ref bu, ref client, ref write_function); } - ); + , headers // (optionally) - specify extracted headers + // , null //with null here, parsing headers from readData or ReadRawBytes, in HttpRequest.cs (test) + + //Binary request contains bytes, but not contains text, text request contains text, but not contains bytes. + //If text, it will be convert to bytes. See HttpConnection.cs + // , is_binary_request // (optionally) - specify "true", if binary request, and "false" if text-request. + // , false //, true //set this binary flag for any request (test). + ); if(is_lite_server == false && ConnectionAdded != null){ ConnectionAdded(connect_client); diff --git a/pages/test_echo_handlers.html b/pages/test_echo_handlers.html new file mode 100644 index 00000000..1fde0997 --- /dev/null +++ b/pages/test_echo_handlers.html @@ -0,0 +1,2546 @@ + + + + + +See tests in console.log(F12 button).
+All tests, must to be "true".

+ + + + \ No newline at end of file