using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; namespace Tesses.Http { public sealed class HTTPClientRequest { HttpParser p; internal HTTPClientRequest(HttpParser parser) { p=parser; } public Dictionary> Headers {get {return p.SentHeaders;}} public void WriteRequestBody(string data) { WriteRequestBody(Encoding.UTF8.GetBytes(data)); } public HTTPClientRequest WithHeader(string key,string value) { Headers.Add(key,value); return this; } public HTTPClientRequest WithRange(long? start,long? end) { string _s=""; string _e=""; if(start.HasValue) { _s=start.Value.ToString(); } if(end.HasValue) { _e =end.Value.ToString(); } string ohdr=$"{_s}-{_e}"; WithHeader("Range",ohdr); return this; } public void WriteRequestBody(object o) { WithContentType("application/json").WriteRequestBody(JsonConvert.SerializeObject(o)); } public void WriteRequestBody(byte[] data) { WriteRequestBody(new MemoryStream(data)); } public void WriteRequestBody(Stream strm) { //we need to write request body Headers.Add("Content-Length",strm.Length.ToString()); p.SendHeaders(); p.WriteBody(strm); p.ReceiveHeaders(); } public HTTPClientRequest WithContentType(string content_type) { Headers.Add("Content-Type",content_type); return this; } public void WriteUrlEncodedPost(Dictionary> args) { string urlencoded= string.Join("&",args.Select>,string>(e=>{ StringBuilder b=new StringBuilder(); foreach(var item in e.Value) { b.Append($"{e.Key}={WebUtility.UrlEncode(item)}"); } return b.ToString(); })); WithContentType("application/x-www-form-urlencoded").WriteRequestBody(urlencoded); } public void WriteEmptyRequest() { p.SendHeaders(); p.ReceiveHeaders(); } public RequestLine RequestLine {get{return p.SentHeaders.FirstLine;} set{p.SentHeaders.FirstLine=value;}} } public sealed class HTTPClient : IDisposable { TcpClient client; Stream strm; HttpParser parser; private HTTPClient(string method,string url) { string host=""; int port; string hostHdr; Uri uri=new Uri(url); hostHdr = uri.Host; host=hostHdr; bool isSecure; isSecure = uri.Scheme != "http" && uri.Scheme != "ws"; if(((uri.Scheme == "http") && uri.Port != 80 ) || ((uri.Scheme == "https") && uri.Port != 443)) { hostHdr += $":{uri.Port}"; port = uri.Port; }else{ if(uri.Scheme=="http") { port=80; } else{ port=443; } } client=new TcpClient(); client.Connect(host,port); if(isSecure) { var m=new SslStream(client.GetStream()); m.AuthenticateAsClient(host); strm=m; }else{ strm = client.GetStream(); } parser=new HttpParser(strm); parser.SentHeaders=new HeaderCollection(); parser.SentHeaders.FirstLine = new RequestLine(method,uri.PathAndQuery,"HTTP/1.1"); Request=new HTTPClientRequest(parser); Response=new HTTPClientResponse(parser); Request.Headers.Add("Host",hostHdr); } public static HTTPClient Open(string method,string url) { return new HTTPClient(method,url); } public HTTPClientRequest Request {get;private set;} public HTTPClientResponse Response {get;private set;} public void SendToServer(ServerContext ctx,bool replaceHost=false) { //Send Request to Server foreach(var hdr in ctx.Request.Headers) { if(hdr.Key != "Host") { if(parser.SentHeaders.ContainsKey(hdr.Key)) { parser.SentHeaders[hdr.Key].Clear(); } foreach(var value in hdr.Value) { parser.SentHeaders.Add(hdr.Key,value); } } } if(replaceHost) { if(parser.SentHeaders.ContainsKey("Host") && ctx.Request.Headers.ContainsKey("Host")) { parser.SentHeaders["Host"].Clear(); parser.SentHeaders.Add("Host",ctx.Request.Headers["Host"][0]); } } //we will send to the second server we are reverse proxing //we need to set dest Method RequestLine req=parser.SentHeaders.FirstLine; parser.SentHeaders.FirstLine = new RequestLine(ctx.Request.RequestLine.Method,req.Path,req.HttpVersion); parser.SendHeaders(); if(ctx.Request.Headers.ContainsKey("Content-Length") && ctx.Request.RequestLine.Method != "GET") { //not a get request and has content-length //so we must copy it parser.WriteBody(ctx.Request.GetRequestStream()); } //we will write the response from HTTPClient to ServerContext parser.ReceiveHeaders(); ctx.Response.Headers.Clear(); ctx.Response.StatusLine = parser.ReceivedHeaders.FirstLine; foreach(var hdr in parser.ReceivedHeaders) { foreach(var value in hdr.Value) { ctx.Response.Headers.Add(hdr.Key,value); } } ctx.p.SendHeaders(); if(ctx.Response.Headers.ContainsKey("Content-Type")) { ctx.p.WriteBody(parser.ReadBody()); } } public static HTTPClient Open(HTTPServer server,string method,string path) { string myUrl; if(server.LocalEndPoint.Address.ToString()=="0.0.0.0") { //we can access via 127.0.0.1 myUrl = $"http://127.0.0.1:{server.LocalEndPoint.Port}/{path.TrimStart('/')}"; }else{ //we can access via X.X.X.X myUrl = $"http://{server.LocalEndPoint.Address.ToString()}:{server.LocalEndPoint.Port}/{path.TrimStart('/')}"; } return HTTPClient.Open(method,myUrl); } public void Dispose() { try{ if(strm != null) strm.Dispose(); }catch(Exception ex) { _=ex; } try{ if(client != null) client.Dispose(); }catch(Exception ex) { _=ex; } } } public class HTTPClientResponse { HttpParser p; public HTTPClientResponse(HttpParser parser) { p=parser; } public Dictionary> Headers {get{return p.ReceivedHeaders;}} public StatusLine StatusLine {get{return p.ReceivedHeaders.FirstLine;} set{p.ReceivedHeaders.FirstLine=value;}} public void DownloadFile(string file) { using(var f = File.Create(file)) { DownloadFile(f); } } public void DownloadFile(Stream strm) { var strm0=GetResponseStream(); strm0.CopyTo(strm); } public Stream GetResponseStream() { return p.ReadBody(); } public string GetResponseString() { using(var sr=new StreamReader(GetResponseStream())) { return sr.ReadToEnd(); } } public T GetResponseJson() { return JsonConvert.DeserializeObject(GetResponseString()); } } public sealed class HTTPPassthroughReverseProxy { ServerContext ctx; string host=""; int port; string hostHdr; bool useSrcHost; bool isSecure; string path; public HTTPPassthroughReverseProxy(ServerContext ctx,string url,bool useSrcHost) { Uri uri=new Uri(url); hostHdr = uri.Host; host=hostHdr; this.useSrcHost = useSrcHost; isSecure = uri.Scheme != "http" && uri.Scheme != "ws"; if(((uri.Scheme == "http" || uri.Scheme == "ws") && uri.Port != 80 ) || ((uri.Scheme == "https" || uri.Scheme == "wss") && uri.Port != 443)) { hostHdr += $":{uri.Port}"; port = uri.Port; }else{ if(uri.Scheme=="http" || uri.Scheme == "ws") { port=80; } else{ port=443; } } path = uri.PathAndQuery; ctx.Request.RequestLine=new RequestLine(ctx.Request.RequestLine.Method,path,ctx.Request.RequestLine.HttpVersion); this.ctx=ctx; } public void Exchange(CancellationToken token=default(CancellationToken)) { using (TcpClient clt=new TcpClient()) { if(!useSrcHost) { ctx.Request.Headers.Remove("Host"); ctx.Request.Headers.Add("Host",hostHdr); } clt.Connect(host,port); Stream strm; if(isSecure) { var m=new SslStream(clt.GetStream()); m.AuthenticateAsClient(host); strm=m; }else{ strm = clt.GetStream(); } using(StreamPiper piper=new StreamPiper(ctx.GetRawStreamWithHeaders(),strm)){ piper.Pipe(token); } } } } }