Statistics
| Branch: | Tag: | Revision:

root / Assets / Plugins / UnityHTTP / Request.cs @ 9:37719e88568d

History | View | Annotate | Download (10.6 kB)

1
using UnityEngine;
2
using System;
3
using System.IO;
4
using System.Text;
5
using System.Collections;
6
using System.Collections.Generic;
7
using System.Net.Sockets;
8
using System.Globalization;
9
using System.Threading;
10
using System.Net.Security;
11
using System.Security.Authentication;
12
using System.Security.Cryptography.X509Certificates;
13
14
namespace HTTP
15
{
16
	public class HTTPException : Exception
17
	{
18
		public HTTPException (string message) : base(message)
19
		{
20
		}
21
	}
22
23
	public enum RequestState {
24
		Waiting, Reading, Done
25
	}
26
27
	public class Request
28
	{
29
        public static bool LogAllRequests = false;
30
        public static bool VerboseLogging = false;
31
32
		public CookieJar cookieJar = CookieJar.Instance;
33
		public string method = "GET";
34
		public string protocol = "HTTP/1.1";
35
		public byte[] bytes;
36
		public Uri uri;
37
		public static byte[] EOL = { (byte)'\r', (byte)'\n' };
38
		public Response response = null;
39
		public bool isDone = false;
40
		public int maximumRetryCount = 8;
41
		public bool acceptGzip = true;
42
		public bool useCache = false;
43
		public Exception exception = null;
44
		public RequestState state = RequestState.Waiting;
45
        public long responseTime = 0; // in milliseconds
46
		public bool synchronous = false;
47
48
		public Action< HTTP.Request > completedCallback = null;
49
50
		Dictionary<string, List<string>> headers = new Dictionary<string, List<string>> ();
51
		static Dictionary<string, string> etags = new Dictionary<string, string> ();
52
53
		public Request (string method, string uri)
54
		{
55
			this.method = method;
56
			this.uri = new Uri (uri);
57
		}
58
59
		public Request (string method, string uri, bool useCache)
60
		{
61
			this.method = method;
62
			this.uri = new Uri (uri);
63
			this.useCache = useCache;
64
		}
65
66
		public Request (string method, string uri, byte[] bytes)
67
		{
68
			this.method = method;
69
			this.uri = new Uri (uri);
70
			this.bytes = bytes;
71
		}
72
73
        public Request( string method, string uri, WWWForm form )
74
        {
75
			this.method = method;
76
			this.uri = new Uri (uri);
77
			this.bytes = form.data;
78
            foreach ( DictionaryEntry entry in form.headers )
79
            {
80
                this.AddHeader( (string)entry.Key, (string)entry.Value );
81
            }
82
        }
83
84
        public Request( string method, string uri, Hashtable data )
85
        {
86
            this.method = method;
87
            this.uri = new Uri( uri );
88
            this.bytes = Encoding.UTF8.GetBytes( JSON.JsonEncode( data ) );
89
            this.AddHeader( "Content-Type", "application/json" );
90
        }
91
        
92
		public void AddHeader (string name, string value)
93
		{
94
			name = name.ToLower ().Trim ();
95
			value = value.Trim ();
96
			if (!headers.ContainsKey (name))
97
				headers[name] = new List<string> ();
98
			headers[name].Add (value);
99
		}
100
101
		public string GetHeader (string name)
102
		{
103
			name = name.ToLower ().Trim ();
104
			if (!headers.ContainsKey (name))
105
				return "";
106
			return headers[name][0];
107
		}
108
109
        public List< string > GetHeaders()
110
        {
111
            List< string > result = new List< string >();
112
            foreach (string name in headers.Keys) {
113
				foreach (string value in headers[name]) {
114
                    result.Add( name + ": " + value );
115
				}
116
			}
117
118
            return result;
119
        }
120
121
		public List<string> GetHeaders (string name)
122
		{
123
			name = name.ToLower ().Trim ();
124
			if (!headers.ContainsKey (name))
125
				headers[name] = new List<string> ();
126
			return headers[name];
127
		}
128
129
		public void SetHeader (string name, string value)
130
		{
131
			name = name.ToLower ().Trim ();
132
			value = value.Trim ();
133
			if (!headers.ContainsKey (name))
134
				headers[name] = new List<string> ();
135
			headers[name].Clear ();
136
			headers[name].TrimExcess();
137
			headers[name].Add (value);
138
		}
139
140
        // TODO: get rid of this when Unity's default monodevelop supports default arguments
141
        public void Send()
142
        {
143
            Send( null );
144
        }
145
		
146
		private void GetResponse() {
147
            System.Diagnostics.Stopwatch curcall = new System.Diagnostics.Stopwatch();
148
	        curcall.Start();
149
			try {
150
151
				var retry = 0;
152
				while (++retry < maximumRetryCount) {
153
					if (useCache) {
154
						string etag = "";
155
						if (etags.TryGetValue (uri.AbsoluteUri, out etag)) {
156
							SetHeader ("If-None-Match", etag);
157
						}
158
					}
159
160
					SetHeader ("Host", uri.Host);
161
162
					var client = new TcpClient ();
163
					client.Connect (uri.Host, uri.Port);
164
					using (var stream = client.GetStream ()) {
165
						var ostream = stream as Stream;
166
						if (uri.Scheme.ToLower() == "https") {
167
							ostream = new SslStream (stream, false, new RemoteCertificateValidationCallback (ValidateServerCertificate));
168
							try {
169
								var ssl = ostream as SslStream;
170
								ssl.AuthenticateAsClient (uri.Host);
171
							} catch (Exception e) {
172
								Debug.LogError ("Exception: " + e.Message);
173
								return;
174
							}
175
						}
176
						WriteToStream (ostream);
177
						response = new Response ();
178
						response.request = this;
179
						state = RequestState.Reading;
180
						response.ReadFromStream(ostream);
181
					}
182
					client.Close ();
183
184
					switch (response.status) {
185
					case 307:
186
					case 302:
187
					case 301:
188
						uri = new Uri (response.GetHeader ("Location"));
189
						continue;
190
					default:
191
						retry = maximumRetryCount;
192
						break;
193
					}
194
				}
195
				if (useCache) {
196
					string etag = response.GetHeader ("etag");
197
					if (etag.Length > 0)
198
						etags[uri.AbsoluteUri] = etag;
199
				}
200
201
			} catch (Exception e) {
202
				Console.WriteLine ("Unhandled Exception, aborting request.");
203
				Console.WriteLine (e);
204
				exception = e;
205
				response = null;
206
			}
207
			state = RequestState.Done;
208
			isDone = true;
209
            responseTime = curcall.ElapsedMilliseconds;
210
211
            if ( completedCallback != null )
212
            {
213
				if (synchronous) {
214
					completedCallback(this);
215
				} else {
216
                    // we have to use this dispatcher to avoid executing the callback inside this worker thread
217
	                ResponseCallbackDispatcher.Singleton.requests.Enqueue( this );
218
				}
219
            }
220
221
            if ( LogAllRequests )
222
            {
223
#if !UNITY_EDITOR
224
				System.Console.WriteLine("NET: " + InfoString( VerboseLogging ));
225
#else
226
                if ( response != null && response.status >= 200 && response.status < 300 )
227
                {
228
                    Debug.Log( InfoString( VerboseLogging ) );
229
                }
230
                else if ( response != null && response.status >= 400 )
231
                {
232
                    Debug.LogError( InfoString( VerboseLogging ) );
233
                }
234
                else
235
                {
236
                    Debug.LogWarning( InfoString( VerboseLogging ) );
237
                }
238
#endif
239
            }			
240
		}
241
		
242
		public void Send( Action< HTTP.Request > callback )
243
		{
244
			
245
            if (!synchronous && callback != null && ResponseCallbackDispatcher.Singleton == null )
246
            {
247
                ResponseCallbackDispatcher.Init();
248
            }
249
250
            completedCallback = callback;
251
252
			isDone = false;
253
			state = RequestState.Waiting;
254
			if (acceptGzip)
255
				SetHeader ("Accept-Encoding", "gzip");
256
257
			if ( this.cookieJar != null )
258
			{
259
				List< Cookie > cookies = this.cookieJar.GetCookies( new CookieAccessInfo( uri.Host, uri.AbsolutePath ) );
260
								string cookieString = this.GetHeader( "cookie" );
261
				for ( int cookieIndex = 0; cookieIndex < cookies.Count; ++cookieIndex )
262
				{
263
					if ( cookieString.Length > 0 && cookieString[ cookieString.Length - 1 ] != ';' )
264
					{
265
						cookieString += ';';
266
					}
267
					cookieString += cookies[ cookieIndex ].name + '=' + cookies[ cookieIndex ].value + ';';
268
				}
269
		        SetHeader( "cookie", cookieString );
270
		    }
271
272
			if ( bytes != null && bytes.Length > 0 && GetHeader ("Content-Length") == "" ) {
273
                SetHeader( "Content-Length", bytes.Length.ToString() );
274
            }
275
276
            if ( GetHeader( "User-Agent" ) == "" ) {
277
                SetHeader( "User-Agent", "UnityWeb 1.0 ( Unity " + Application.unityVersion + " ) ( " + SystemInfo.operatingSystem + " )" );
278
            }
279
280
            if ( GetHeader( "Connection" ) == "" ) {
281
                SetHeader( "Connection", "close" );
282
            }
283
			
284
			// Basic Authorization
285
			if (!String.IsNullOrEmpty(uri.UserInfo)) {	
286
				SetHeader("Authorization", "Basic " + System.Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(uri.UserInfo)));
287
			}
288
			
289
			if (synchronous) {
290
				GetResponse();
291
			} else {
292
				ThreadPool.QueueUserWorkItem (new WaitCallback ( delegate(object t) {
293
					GetResponse();
294
				})); 
295
			}
296
		}
297
298
		public string Text {
299
			set { bytes = System.Text.Encoding.UTF8.GetBytes (value); }
300
		}
301
302
303
		public static bool ValidateServerCertificate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
304
		{
305
#if !UNITY_EDITOR
306
            System.Console.WriteLine( "NET: SSL Cert: " + sslPolicyErrors.ToString() );
307
#else
308
			Debug.LogWarning("SSL Cert Error: " + sslPolicyErrors.ToString ());
309
#endif
310
			return true;
311
		}
312
313
		void WriteToStream (Stream outputStream)
314
		{
315
			var stream = new BinaryWriter (outputStream);
316
			stream.Write (ASCIIEncoding.ASCII.GetBytes (method.ToUpper () + " " + uri.PathAndQuery + " " + protocol));
317
			stream.Write (EOL);
318
319
			foreach (string name in headers.Keys) {
320
				foreach (string value in headers[name]) {
321
					stream.Write (ASCIIEncoding.ASCII.GetBytes (name));
322
					stream.Write (':');
323
					stream.Write (ASCIIEncoding.ASCII.GetBytes (value));
324
					stream.Write (EOL);
325
				}
326
			}
327
328
            stream.Write (EOL);
329
330
			if (bytes != null && bytes.Length > 0) {
331
				stream.Write (bytes);
332
			}
333
		}
334
335
        private static string[] sizes = { "B", "KB", "MB", "GB" };
336
        public string InfoString( bool verbose )
337
        {
338
            string status = isDone && response != null ? response.status.ToString() : "---";
339
            string message = isDone && response != null ? response.message : "Unknown";
340
            double size = isDone && response != null && response.bytes != null ? response.bytes.Length : 0.0f;
341
342
            int order = 0;
343
            while ( size >= 1024.0f && order + 1 < sizes.Length )
344
            {
345
                ++order;
346
                size /= 1024.0f;
347
            }
348
349
            string sizeString = String.Format( "{0:0.##}{1}", size, sizes[ order ] );
350
351
            string result = uri.ToString() + " [ " + method.ToUpper() + " ] [ " + status + " " + message + " ] [ " + sizeString + " ] [ " + responseTime + "ms ]";
352
353
            if ( verbose && response != null )
354
            {
355
                result += "\n\nRequest Headers:\n\n" + String.Join( "\n", GetHeaders().ToArray() );
356
                result += "\n\nResponse Headers:\n\n" + String.Join( "\n", response.GetHeaders().ToArray() );
357
358
                if ( response.Text != null )
359
                {
360
                    result += "\n\nResponse Body:\n" + response.Text;
361
                }
362
            }
363
364
            return result;
365
        }
366
	}
367
}