2
2
{
3
3
using AngleSharp . Attributes ;
4
4
using AngleSharp . Dom ;
5
+ using AngleSharp . Dom . Events ;
5
6
using AngleSharp . Network ;
6
7
using System ;
7
8
using System . Collections . Generic ;
8
9
using System . IO ;
10
+ using System . Net ;
11
+ using System . Text ;
12
+ using System . Threading ;
13
+ using System . Threading . Tasks ;
9
14
10
15
/// <summary>
11
16
/// Defines the XHR. For more information see:
15
20
[ DomExposed ( "Window" ) ]
16
21
[ DomExposed ( "DedicatedWorker" ) ]
17
22
[ DomExposed ( "SharedWorker" ) ]
18
- public sealed class XmlHttpRequest : XmlHttpRequestEventTarget , IRequest
23
+ public sealed class XmlHttpRequest : XmlHttpRequestEventTarget
19
24
{
20
25
#region Fields
21
26
22
27
readonly Dictionary < String , String > _headers ;
23
28
readonly IWindow _window ;
29
+ readonly CancellationTokenSource _cancel ;
24
30
31
+ DocumentRequest _request ;
25
32
RequesterState _readyState ;
26
33
Int32 _timeout ;
27
34
Boolean _credentials ;
28
- IResponse _response ;
29
35
HttpMethod _method ;
30
36
Url _url ;
31
37
Boolean _async ;
32
38
String _mime ;
33
- Stream _body ;
39
+ HttpStatusCode _responseStatus ;
40
+ String _responseUrl ;
41
+ String _responseText ;
34
42
35
43
#endregion
36
44
@@ -45,10 +53,14 @@ public XmlHttpRequest(IWindow window)
45
53
_window = window ;
46
54
_async = true ;
47
55
_method = HttpMethod . Get ;
56
+ _cancel = new CancellationTokenSource ( ) ;
57
+ _headers = new Dictionary < String , String > ( ) ;
48
58
_url = null ;
49
- _response = null ;
50
59
_mime = null ;
60
+ _responseUrl = String . Empty ;
61
+ _responseText = String . Empty ;
51
62
_readyState = RequesterState . Unsent ;
63
+ _responseStatus = ( HttpStatusCode ) 0 ;
52
64
_credentials = false ;
53
65
_timeout = 45000 ;
54
66
}
@@ -57,6 +69,14 @@ public XmlHttpRequest(IWindow window)
57
69
58
70
#region Properties
59
71
72
+ /// <summary>
73
+ /// Gets if response headers are accessible.
74
+ /// </summary>
75
+ public Boolean HasResponseHeaders
76
+ {
77
+ get { return _readyState == RequesterState . Loading || _readyState == RequesterState . Done ; }
78
+ }
79
+
60
80
/// <summary>
61
81
/// Adds or removes the handler for the readystatechange event.
62
82
/// </summary>
@@ -74,6 +94,11 @@ public event DomEventHandler ReadyStateChanged
74
94
public RequesterState ReadyState
75
95
{
76
96
get { return _readyState ; }
97
+ private set
98
+ {
99
+ _readyState = value ;
100
+ Fire ( ReadyStateChangeEvent ) ;
101
+ }
77
102
}
78
103
79
104
/// <summary>
@@ -120,7 +145,7 @@ public XmlHttpRequestResponseType ResponseType
120
145
[ DomName ( "responseURL" ) ]
121
146
public String ResponseUrl
122
147
{
123
- get { return _response != null ? _response . Address . Href : String . Empty ; }
148
+ get { return _responseUrl ; }
124
149
}
125
150
126
151
/// <summary>
@@ -129,7 +154,7 @@ public String ResponseUrl
129
154
[ DomName ( "status" ) ]
130
155
public Int32 StatusCode
131
156
{
132
- get { return _response != null ? ( Int32 ) _response . StatusCode : 0 ; }
157
+ get { return ( Int32 ) _responseStatus ; }
133
158
}
134
159
135
160
/// <summary>
@@ -138,7 +163,7 @@ public Int32 StatusCode
138
163
[ DomName ( "statusText" ) ]
139
164
public String StatusText
140
165
{
141
- get { return _response != null ? _response . StatusCode . ToString ( ) : String . Empty ; }
166
+ get { return StatusCode != 0 ? _responseStatus . ToString ( ) : String . Empty ; }
142
167
}
143
168
144
169
/// <summary>
@@ -147,7 +172,7 @@ public String StatusText
147
172
[ DomName ( "response" ) ]
148
173
public Object Response
149
174
{
150
- get { return _response != null ? null : String . Empty ; }
175
+ get { return null ; }
151
176
}
152
177
153
178
/// <summary>
@@ -156,7 +181,7 @@ public Object Response
156
181
[ DomName ( "responseText" ) ]
157
182
public String ResponseText
158
183
{
159
- get { return _response != null ? null : String . Empty ; }
184
+ get { return _responseText ; }
160
185
}
161
186
162
187
/// <summary>
@@ -178,21 +203,11 @@ public IDocument ResponseXml
178
203
[ DomName ( "abort" ) ]
179
204
public void Abort ( )
180
205
{
181
- //TODO
182
- }
183
-
184
- /// <summary>
185
- /// Opens a new request with the provided method and URL.
186
- /// </summary>
187
- /// <param name="method">The method to use.</param>
188
- /// <param name="url">The URL to send to request to.</param>
189
- [ DomName ( "open" ) ]
190
- public void Open ( String method , String url )
191
- {
192
- if ( Enum . TryParse ( method , true , out _method ) == false )
193
- _method = HttpMethod . Get ;
194
-
195
- _url = Url . Create ( url ) ;
206
+ if ( _readyState == RequesterState . Loading )
207
+ {
208
+ _cancel . Cancel ( ) ;
209
+ Fire ( AbortEvent ) ;
210
+ }
196
211
}
197
212
198
213
/// <summary>
@@ -204,13 +219,20 @@ public void Open(String method, String url)
204
219
/// <param name="username">Should a username be used?</param>
205
220
/// <param name="password">Should a password be used?</param>
206
221
[ DomName ( "open" ) ]
207
- public void Open ( String method , String url , Boolean async , String username = null , String password = null )
222
+ public void Open ( String method , String url , Boolean async = true , String username = null , String password = null )
208
223
{
209
- Open ( method , url ) ;
224
+ if ( _readyState == RequesterState . Unsent )
225
+ {
226
+ ReadyState = RequesterState . Opened ;
210
227
211
- _async = async ;
212
- _url . UserName = username ;
213
- _url . Password = password ;
228
+ if ( Enum . TryParse ( method , true , out _method ) == false )
229
+ _method = HttpMethod . Get ;
230
+
231
+ _url = Url . Create ( url ) ;
232
+ _async = async ;
233
+ _url . UserName = username ;
234
+ _url . Password = password ;
235
+ }
214
236
}
215
237
216
238
/// <summary>
@@ -220,8 +242,36 @@ public void Open(String method, String url, Boolean async, String username = nul
220
242
[ DomName ( "send" ) ]
221
243
public void Send ( Object body = null )
222
244
{
223
- _body = Stream . Null ;
245
+ if ( _readyState != RequesterState . Opened )
246
+ return ;
224
247
248
+ var requestBody = Serialize ( body ) ;
249
+ var mimeType = default ( String ) ;
250
+ var loader = GetLoader ( ) ;
251
+
252
+ if ( loader != null )
253
+ {
254
+ var request = new DocumentRequest ( _url )
255
+ {
256
+ Body = requestBody ,
257
+ Method = _method ,
258
+ MimeType = mimeType ,
259
+ Referer = _window . Document . DocumentUri ,
260
+ } ;
261
+
262
+ foreach ( var header in _headers )
263
+ request . Headers [ header . Key ] = header . Value ;
264
+
265
+ _headers . Clear ( ) ;
266
+ _cancel . CancelAfter ( _timeout ) ;
267
+
268
+ Fire ( LoadStartEvent ) ;
269
+ ReadyState = RequesterState . HeadersReceived ;
270
+ var connection = Receive ( loader , request , _cancel . Token ) ;
271
+
272
+ if ( ! _async )
273
+ connection . Wait ( ) ;
274
+ }
225
275
}
226
276
227
277
/// <summary>
@@ -232,7 +282,8 @@ public void Send(Object body = null)
232
282
[ DomName ( "setRequestHeader" ) ]
233
283
public void SetRequestHeader ( String name , String value )
234
284
{
235
- _headers [ name ] = value ;
285
+ if ( _readyState == RequesterState . Opened )
286
+ _headers [ name ] = value ;
236
287
}
237
288
238
289
/// <summary>
@@ -245,7 +296,7 @@ public String GetResponseHeader(String name)
245
296
{
246
297
var value = default ( String ) ;
247
298
248
- if ( _response != null && _response . Headers . TryGetValue ( name , out value ) )
299
+ if ( HasResponseHeaders && _headers . TryGetValue ( name , out value ) )
249
300
return value ;
250
301
251
302
return String . Empty ;
@@ -258,9 +309,9 @@ public String GetResponseHeader(String name)
258
309
[ DomName ( "getAllResponseHeaders" ) ]
259
310
public String GetAllResponseHeaders ( )
260
311
{
261
- if ( _response != null )
312
+ if ( HasResponseHeaders )
262
313
{
263
- var headers = _response . Headers ;
314
+ var headers = _headers ;
264
315
var lines = new String [ headers . Count ] ;
265
316
var index = 0 ;
266
317
@@ -280,31 +331,89 @@ public String GetAllResponseHeaders()
280
331
[ DomName ( "overrideMimeType" ) ]
281
332
public void OverrideMimeType ( String mime )
282
333
{
283
- _mime = mime ;
334
+ if ( _readyState == RequesterState . Opened )
335
+ _mime = mime ;
284
336
}
285
337
286
338
#endregion
287
339
288
- #region Request
340
+ #region Helpers
289
341
290
- Url IRequest . Address
342
+ async Task Receive ( IDocumentLoader loader , DocumentRequest request , CancellationToken cancel )
291
343
{
292
- get { return _url ; }
344
+ try
345
+ {
346
+ var response = await loader . LoadAsync ( request , cancel ) . ConfigureAwait ( false ) ;
347
+
348
+ if ( response != null )
349
+ {
350
+ using ( response )
351
+ {
352
+ foreach ( var header in response . Headers )
353
+ _headers [ header . Key ] = header . Value ;
354
+
355
+ _responseUrl = response . Address . Href ;
356
+ _responseStatus = response . StatusCode ;
357
+ ReadyState = RequesterState . Loading ;
358
+
359
+ using ( var ms = new MemoryStream ( ) )
360
+ {
361
+ await response . Content . CopyToAsync ( ms , 16384 , cancel ) . ConfigureAwait ( false ) ;
362
+ ms . Seek ( 0 , SeekOrigin . Begin ) ;
363
+
364
+ using ( var reader = new StreamReader ( ms ) )
365
+ _responseText = reader . ReadToEnd ( ) ;
366
+ }
367
+ }
368
+
369
+ Fire ( LoadEndEvent ) ;
370
+ ReadyState = RequesterState . Done ;
371
+ Fire ( LoadEvent ) ;
372
+ }
373
+ else
374
+ {
375
+ ReadyState = RequesterState . Done ;
376
+ Fire ( ErrorEvent ) ;
377
+ }
378
+ }
379
+ catch ( TaskCanceledException )
380
+ {
381
+ ReadyState = RequesterState . Done ;
382
+ Fire ( TimeoutEvent ) ;
383
+ }
293
384
}
294
385
295
- Stream IRequest . Content
386
+ IDocumentLoader GetLoader ( )
296
387
{
297
- get { return _body ; }
388
+ if ( _window == null )
389
+ return null ;
390
+
391
+ var document = _window . Document ;
392
+
393
+ if ( document == null )
394
+ return null ;
395
+
396
+ var context = document . Context ;
397
+ return context != null ? context . Loader : null ;
298
398
}
299
399
300
- Dictionary < String , String > IRequest . Headers
400
+ static Stream Serialize ( Object body )
301
401
{
302
- get { return _headers ; }
402
+ if ( body != null )
403
+ {
404
+ //TODO Different Types?
405
+ var content = body . ToString ( ) ;
406
+ var bytes = Encoding . UTF8 . GetBytes ( content ) ;
407
+ return new MemoryStream ( bytes ) ;
408
+ }
409
+
410
+ return Stream . Null ;
303
411
}
304
412
305
- HttpMethod IRequest . Method
413
+ void Fire ( String eventName )
306
414
{
307
- get { return _method ; }
415
+ var evt = new Event ( eventName ) ;
416
+ Dispatch ( evt ) ;
308
417
}
309
418
310
419
#endregion
0 commit comments