Skip to content

Instantly share code, notes, and snippets.

@xiaobin83
Last active December 28, 2016 15:12
Show Gist options
  • Save xiaobin83/ad784e7210f4d6f4b72c2a2cbfc391ae to your computer and use it in GitHub Desktop.
Save xiaobin83/ad784e7210f4d6f4b72c2a2cbfc391ae to your computer and use it in GitHub Desktop.
Simple HttpWebRequest wrapper.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text;
using common;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
namespace federation
{
public class WebRequest2
{
public class Context
{
public bool muteErrorLog = false;
public bool reportingError = false; // sally, should use another method, for example, using error reporting callback to get rid of logging
public float timeOutTime = Config.webRequestTimeoutTime;
public System.Uri srv;
public string function;
public Method method;
public Dictionary<string, object> parameters;
public WebRequestRespondedCallback callback;
public Auth auth;
public Context parent;
public Context Clone(System.Uri srv, string function, Method method, Dictionary<string, object> parameters, WebRequestRespondedCallback callback, Auth auth)
{
var cloned = MemberwiseClone() as Context;
cloned.srv = srv;
cloned.function = function;
cloned.method = method;
cloned.parameters = parameters;
cloned.callback = callback;
cloned.auth = auth;
cloned.parent = this;
return cloned;
}
}
static Context defaultContext = new Context();
public enum Method
{
GET,
POST,
}
public delegate void WebRequestRespondedCallback(WebExceptionStatus s, HttpStatusCode code, SimpleJSON.JSONClass payload, CookieCollection cookies, WebHeaderCollection headers, Context context);
static bool acceptAnyCert_ = false;
public static bool SetupServerCertificateValidationCallback(bool acceptAnyCert = false)
{
#if WEBREQUEST_USE_SELF_CERT
Debug.LogWarning("Always accept any certification.")
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
return true;
#else
var old = acceptAnyCert_;
acceptAnyCert_ = acceptAnyCert;
if (acceptAnyCert_)
{
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
}
else
{
ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCallback;
}
return old;
#endif
}
// all client side error will be reported to federation.Error
public static bool IsClientSideError(HttpStatusCode code)
{
return (int)code >= 400 && (int)code < 500;
}
public static void MakeRequestTo(
System.Uri srv,
string function,
Method method,
Dictionary<string, object> parameter,
WebRequestRespondedCallback callback,
Auth auth = null,
Context context = null)
{
if (context == null) context = defaultContext;
context = context.Clone(srv, function, method, parameter, callback, auth);
if (srv != null)
{
common.CommonApplication.StartUnbreakableCoroutine(MakeRequestTo_(context));
}
else
{
if (!context.muteErrorLog)
Error.instance.Post(auth, srv, function, parameter, HttpStatusCode.Unused, null, "Service url not found!");
Eve.instance.SetExpire();
if (callback != null)
callback(WebExceptionStatus.ConnectFailure, HttpStatusCode.Unused, null, null, null, context);
}
}
class GetResponse : UnityEngine.CustomYieldInstruction
{
System.DateTime expireTime;
HttpWebRequest req;
HttpWebResponse resp;
System.IO.MemoryStream outputStream = new System.IO.MemoryStream();
static SimpleJSON.JSONClass emptyPayload = new SimpleJSON.JSONClass();
SimpleJSON.JSONClass payload_;
public SimpleJSON.JSONClass payload
{
get
{
if (payload_ == null)
{
if (outputStream != null)
{
outputStream.Seek(0, System.IO.SeekOrigin.Begin);
var str = new System.IO.StreamReader(outputStream).ReadToEnd();
Debug.LogFormat("RAW RESPONSE: {0}", str);
if (!string.IsNullOrEmpty(str))
{
payload_ = SimpleJSON.JSON.Parse(str) as SimpleJSON.JSONClass;
}
else
{
payload_ = new SimpleJSON.JSONClass();
}
}
outputStream = null;
}
return payload_ != null ? payload_ : emptyPayload;
}
}
byte[] temp = new byte[512]; // managed somewhere
byte[] postData = null;
List<Dictionary<string, byte[]>> attachement = null;
public GetResponse(HttpWebRequest req, byte[] postData, float timeoutTime, List<Dictionary<string, byte[]>> attachement = null)
{
this.expireTime = System.DateTime.Now.AddSeconds(timeoutTime);
this.req = req;
this.postData = postData;
this.attachement = attachement;
if (this.postData != null)
{
try
{
req.BeginGetRequestStream(RequestWriteCallback, this);
}
catch (Exception e)
{
SetRespondedWithClientError(e);
}
}
else
{
DoGetResponse();
}
}
void DoGetResponse()
{
try
{
req.BeginGetResponse(ResponseCallback, this);
}
catch (Exception e)
{
SetRespondedWithClientError(e);
}
}
static void RequestWriteCallback(IAsyncResult ar)
{
var This = (GetResponse)ar.AsyncState;
Debug.Assert(This.postData != null);
try
{
Debug.Log("Writing post data on req " + This.req.RequestUri.ToString());
var stream = This.req.EndGetRequestStream(ar);
if (This.attachement != null)
{
// send attachements with postData
string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
This.req.ContentType = "multipart/form-data; boundary=" + boundary;
byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
var endBoundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--");
// write postData
stream.Write(boundarybytes, 0, boundarybytes.Length);
var postDataHeader = "Content-Type: application/x-www-form-urlencoded\r\n\r\n";
var postDataHeaderBytes = System.Text.Encoding.ASCII.GetBytes(postDataHeader);
stream.Write(This.postData, 0, This.postData.Length);
// write files
const string attachmentHeaderTemplate =
"Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n";
foreach (var fileList in This.attachement)
{
foreach (var file in fileList)
{
stream.Write(boundarybytes, 0, boundarybytes.Length);
string header = string.Format(attachmentHeaderTemplate, "attachment", file.Key);
var headerBytes = System.Text.Encoding.UTF8.GetBytes(header);
stream.Write(headerBytes, 0, headerBytes.Length);
stream.Write(file.Value, 0, file.Value.Length);
}
}
stream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
stream.Close();
}
else
{
This.req.ContentType = "application/x-www-form-urlencoded";
stream.Write(This.postData, 0, This.postData.Length);
stream.Close();
}
This.attachement = null;
This.postData = null;
This.DoGetResponse();
}
catch (Exception e)
{
This.SetRespondedWithClientError(e);
}
}
static void ResponseCallback(IAsyncResult ar)
{
var This = (GetResponse)ar.AsyncState;
try
{
This.resp = This.req.EndGetResponse(ar) as HttpWebResponse;
var stream = This.resp.GetResponseStream();
stream.BeginRead(This.temp, 0, This.temp.Length, StreamReadCallback, This);
}
catch (WebException e)
{
if (e.Response != null) // has error response
{
This.webExceptionStatus = e.Status;
// do not set This.error here, because e.Response will give the detail.
// and This.error also used for check if a client error happens (to save various test)
// replace resp with error resp
This.resp = e.Response as HttpWebResponse;
// read exception returned content
var stream = This.resp.GetResponseStream();
stream.BeginRead(This.temp, 0, This.temp.Length, StreamReadCallback, This);
}
else
{
This.SetRespondedWithClientError(e);
}
}
catch (System.Exception e)
{
This.SetRespondedWithClientError(e);
}
}
static void StreamReadCallback(IAsyncResult ar)
{
var This = (GetResponse)ar.AsyncState;
try
{
var stream = This.resp.GetResponseStream();
var szRead = stream.EndRead(ar);
if (szRead > 0)
{
This.outputStream.Write(This.temp, 0, szRead);
stream.BeginRead(This.temp, 0, This.temp.Length, StreamReadCallback, This);
}
else
{
This.SetResponded(This.resp.StatusCode, This.resp.StatusDescription, This.resp.Cookies, This.resp.Headers);
stream.Close();
This.resp.Close();
}
}
catch (System.Exception e)
{
This.SetRespondedWithClientError(e);
}
}
public WebExceptionStatus webExceptionStatus = WebExceptionStatus.Success;
public HttpStatusCode statusCode;
public string statusDescription;
public CookieCollection cookies;
public WebHeaderCollection headers;
public bool responded = false;
public string error
{
get; private set;
}
void SetResponded(HttpStatusCode code, string description, CookieCollection cookies, WebHeaderCollection headers)
{
this.statusCode = code;
this.statusDescription = description;
this.cookies = cookies;
this.headers = headers;
this.responded = true;
error = null;
}
void SetRespondedWithClientError(System.Exception e)
{
if (e is WebException)
{
var ee = (WebException)e;
webExceptionStatus = ee.Status;
if (ee.Status == WebExceptionStatus.ConnectFailure)
{
Eve.instance.SetExpire(); // ???
}
}
responded = true;
error = e.GetType().ToString() + ": " + e.Message;
}
public override bool keepWaiting
{
get
{
if (!responded)
{
if (System.DateTime.Now > expireTime)
{
try
{
if (resp != null)
{
resp.Close();
resp = null;
}
req.Abort();
req = null;
outputStream = null;
}
catch (Exception e)
{
Debug.LogError("Exception not processed: " + e.Message);
}
SetRespondedWithClientError(new System.Net.WebException("Client detected timeout", System.Net.WebExceptionStatus.Timeout));
}
}
return !responded;
}
}
}
static void LogErrorPayload(Uri url, Method method, Dictionary<string, object> parameter, GetResponse resp)
{
var sb = new System.Text.StringBuilder();
sb.Append("WebRequest failed: ");
sb.Append(resp.statusDescription);
sb.AppendLine();
sb.Append(method);
sb.Append(" ");
sb.Append(url);
sb.AppendLine();
sb.Append("Request payload:");
sb.AppendLine();
if (parameter != null)
{
foreach (var kv in parameter)
{
sb.Append(" ");
sb.Append(kv.Key);
sb.Append("=");
sb.Append(kv.Value);
sb.AppendLine();
}
}
sb.Append("Responding payload:");
sb.AppendLine();
if (resp.payload != null)
sb.Append(resp.payload.ToString());
Debug.LogError(sb.ToString());
}
static bool ValueIsFiles(object value)
{
return value is Dictionary<string, byte[]>;
}
static System.Uri BuildGetQuery(System.Uri url, Dictionary<string, object> parameters)
{
if (parameters != null && parameters.Count > 0)
{
var sb = new StringBuilder();
foreach (var kv in parameters)
{
if (ValueIsFiles(kv.Value))
{
Debug.LogWarningFormat("ignore attachement {1} in GET request", kv.Key);
continue;
}
sb.Append(kv.Key);
sb.Append("=");
sb.Append(kv.Value.ToString());
sb.Append("&");
}
sb.Length = sb.Length - 1;
var ub = new System.UriBuilder(url);
ub.Query = sb.ToString();
url = ub.Uri;
}
return url;
}
static IEnumerator MakeRequestTo_(Context context)
{
Debug.Assert(context.srv != null);
var url = context.srv;
if (!string.IsNullOrEmpty(context.function))
{
url = new System.Uri(context.srv, context.function);
}
HttpWebRequest req = null;
#if !RELEASE_VERSION
var payloadRecord = new System.Text.StringBuilder();
#endif
byte[] postData = null;
List<Dictionary<string, byte[]>> attachments = null;
if (context.method == Method.GET)
{
url = BuildGetQuery(url, context.parameters);
req = System.Net.WebRequest.Create(url.ToString()) as HttpWebRequest;
if (context.auth != null)
{
req.CookieContainer = context.auth.cookieContainer;
}
else
{
req.CookieContainer = new CookieContainer();
}
req.Method = "GET";
}
else if (context.method == Method.POST)
{
req = System.Net.WebRequest.Create(url.ToString()) as HttpWebRequest;
req.Method = "POST";
req.KeepAlive = true;
if (context.auth != null)
{
req.CookieContainer = context.auth.cookieContainer;
}
else
{
req.CookieContainer = new CookieContainer();
}
if (context.parameters != null && context.parameters.Count > 0)
{
var sb = new System.Text.StringBuilder();
var first = true;
foreach (var kv in context.parameters)
{
if (ValueIsFiles(kv.Value))
{
// construct mime here
if (attachments == null)
{
attachments = new List<Dictionary<string, byte[]>>();
}
attachments.Add((Dictionary<string, byte[]>)kv.Value);
}
else
{
if (!first) sb.Append("&");
sb.Append(kv.Key); sb.Append("="); sb.Append(kv.Value);
first = false;
#if !RELEASE_VERSION
payloadRecord.Append(kv.Key); payloadRecord.Append("="); payloadRecord.Append(kv.Value);
payloadRecord.AppendLine();
#endif
}
}
postData = System.Text.Encoding.ASCII.GetBytes(sb.ToString());
}
}
#if RELEASE_VERSION
if (context.reportingError)
Debug.LogError("Start Request: " + req.RequestUri.ToString());
else
Debug.Log("Start Request: " + req.RequestUri.ToString());
#else
var logSb = new System.Text.StringBuilder();
logSb.Append("Start Request: ");
logSb.Append(req.RequestUri.ToString());
logSb.AppendLine();
logSb.Append(" with payload: ");
logSb.Append(payloadRecord.ToString());
logSb.AppendLine();
if (context.auth != null)
{
context.auth.ToString(logSb);
logSb.AppendLine();
}
if (context.reportingError)
Debug.LogError(logSb.ToString());
else
Debug.Log(logSb.ToString());
#endif
var resp = new GetResponse(req, postData, context.timeOutTime, attachments);
yield return resp;
if (context.reportingError)
Debug.LogError("Request Responded: " + req.RequestUri.ToString());
else
Debug.Log("Request Responded: " + req.RequestUri.ToString());
if (!string.IsNullOrEmpty(resp.error))
{
Debug.LogError(resp.error);
if (!context.muteErrorLog)
{
Error.instance.Post(context.auth, context.srv, context.function, context.parameters, HttpStatusCode.Unused, null, resp.error);
}
if (context.callback != null)
context.callback(resp.webExceptionStatus, HttpStatusCode.Unused, null, null, null, context);
}
else if (resp.statusCode == HttpStatusCode.Unauthorized)
{
Debug.Assert(context.auth != null, "An authorized request should have an Auth instance");
Debug.LogWarning("Unauthorized or auth expired. Re-authorize ... ");
LogErrorPayload(url, context.method, context.parameters, resp);
context.auth.PerformAuthorizedAction(
() =>
{
Debug.LogWarning("Authorized, continue request " + context.srv + context.function);
MakeRequestTo(context.srv, context.function, context.method, context.parameters, context.callback, context.auth, context.parent);
},
renewAuth: true);
}
else if (IsClientSideError(resp.statusCode))
{
LogErrorPayload(url, context.method, context.parameters, resp);
if (!context.muteErrorLog)
{
Error.instance.Post(context.auth, context.srv, context.function, context.parameters, resp.statusCode, resp.payload);
}
if (context.callback != null)
context.callback(resp.webExceptionStatus, resp.statusCode, resp.payload, resp.cookies, resp.headers, context);
}
else if (resp.statusCode != HttpStatusCode.OK)
{
LogErrorPayload(url, context.method, context.parameters, resp);
if (context.callback != null)
context.callback(resp.webExceptionStatus, resp.statusCode, resp.payload, resp.cookies, resp.headers, context);
}
else // used by Eve, Auth,
{
if (context.callback != null)
context.callback(resp.webExceptionStatus, resp.statusCode, resp.payload, resp.cookies, resp.headers, context);
}
}
public static void GetViaProxy(System.Uri srv, string function, Dictionary<string, object> parameter, WebRequestRespondedCallback callback, Context context = null)
{
var url = srv;
if (!string.IsNullOrEmpty(function))
{
url = new System.Uri(srv, function);
}
url = BuildGetQuery(url, parameter);
var proxy = new System.UriBuilder(Config.proxyUrl);
proxy.Query = url.ToString();
Get(proxy.Uri, null, null, callback, context);
}
public static void Get(System.Uri srv, string function, Dictionary<string, object> parameter, WebRequestRespondedCallback callback, Context context = null)
{
MakeRequestTo(srv, function, Method.GET, parameter, callback, auth: null, context: context);
}
public static void PostViaProxy(System.Uri srv, string function, Dictionary<string, object> parameter, WebRequestRespondedCallback callback, Context context = null)
{
var url = srv;
if (!string.IsNullOrEmpty(function))
{
url = new System.Uri(srv, function);
}
var proxy = new System.UriBuilder(Config.proxyUrl);
proxy.Query = url.ToString();
Post(proxy.Uri, null, parameter, callback, context);
}
public static void Post(System.Uri srv, string function, Dictionary<string, object> parameter, WebRequestRespondedCallback callback, Context context = null)
{
MakeRequestTo(srv, function, Method.POST, parameter, callback, auth: null, context: context);
}
public static void GetWithAuth(System.Uri srv, string function, Auth auth, Dictionary<string, object> parameter, WebRequestRespondedCallback callback, Context context = null)
{
MakeRequestTo(srv, function, Method.GET, parameter, callback, auth, context);
}
public static void PostWithAuth(System.Uri srv, string function, Auth auth, Dictionary<string, object> parameter, WebRequestRespondedCallback callback, Context context = null)
{
MakeRequestTo(srv, function, Method.POST, parameter, callback, auth, context);
}
static bool DefaultRetryCondition(WebExceptionStatus status, HttpStatusCode code)
{
return status != WebExceptionStatus.Success || code == HttpStatusCode.InternalServerError;
}
public static WebRequestRespondedCallback RetryIfFailed(
WebRequestRespondedCallback callback,
int tries = Config.maxTries,
System.Func<WebExceptionStatus, HttpStatusCode, bool> cond = null,
float nextRetryAfter = Config.retryDurationIncreasement, float retryDurationIncr = Config.retryDurationIncreasement)
{
if (cond == null) cond = DefaultRetryCondition;
return (status, code, _1, _2, _3, context) =>
{
if (status == WebExceptionStatus.Success && code == HttpStatusCode.OK)
{
callback(status, code, _1, _2, _3, context);
}
else
{
tries = tries - 1;
if (tries > 0 && cond(status, code))
{
float delay = nextRetryAfter;
nextRetryAfter += retryDurationIncr;
Debug.LogWarningFormat("Retry will start in {0} seconds ...", delay);
CommonApplication.DelayExecute(
delay,
() => MakeRequestTo(context.srv, context.function, context.method, context.parameters, context.callback, context.auth, context));
}
else
{
callback(status, code, _1, _2, _3, context);
}
}
};
}
static bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
bool isOk = true;
// If there are errors in the certificate chain, look at each error to determine the cause.
if (sslPolicyErrors != SslPolicyErrors.None)
{
for (int i = 0; i < chain.ChainStatus.Length; i++)
{
if (chain.ChainStatus[i].Status != X509ChainStatusFlags.RevocationStatusUnknown)
{
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
bool chainIsValid = chain.Build((X509Certificate2)certificate);
if (!chainIsValid)
{
isOk = false;
}
}
}
}
return isOk;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment