334 lines
10 KiB
C#
334 lines
10 KiB
C#
|
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<string,List<string>> 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<string,List<string>> args)
|
||
|
{
|
||
|
string urlencoded= string.Join("&",args.Select<KeyValuePair<string,List<string>>,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
|
||
|
{
|
||
|
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 class HTTPClientResponse
|
||
|
{
|
||
|
HttpParser p;
|
||
|
public HTTPClientResponse(HttpParser parser)
|
||
|
{
|
||
|
p=parser;
|
||
|
}
|
||
|
|
||
|
public Dictionary<string,List<string>> 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<T>()
|
||
|
{
|
||
|
return JsonConvert.DeserializeObject<T>(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);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|