Skip to content

Commit 4876f2b

Browse files
committed
Implemented XHR closes #14
1 parent fdb8c9e commit 4876f2b

File tree

1 file changed

+153
-44
lines changed

1 file changed

+153
-44
lines changed

AngleSharp.Scripting.JavaScript/Dom/XmlHttpRequest.cs

Lines changed: 153 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
{
33
using AngleSharp.Attributes;
44
using AngleSharp.Dom;
5+
using AngleSharp.Dom.Events;
56
using AngleSharp.Network;
67
using System;
78
using System.Collections.Generic;
89
using System.IO;
10+
using System.Net;
11+
using System.Text;
12+
using System.Threading;
13+
using System.Threading.Tasks;
914

1015
/// <summary>
1116
/// Defines the XHR. For more information see:
@@ -15,22 +20,25 @@
1520
[DomExposed("Window")]
1621
[DomExposed("DedicatedWorker")]
1722
[DomExposed("SharedWorker")]
18-
public sealed class XmlHttpRequest : XmlHttpRequestEventTarget, IRequest
23+
public sealed class XmlHttpRequest : XmlHttpRequestEventTarget
1924
{
2025
#region Fields
2126

2227
readonly Dictionary<String, String> _headers;
2328
readonly IWindow _window;
29+
readonly CancellationTokenSource _cancel;
2430

31+
DocumentRequest _request;
2532
RequesterState _readyState;
2633
Int32 _timeout;
2734
Boolean _credentials;
28-
IResponse _response;
2935
HttpMethod _method;
3036
Url _url;
3137
Boolean _async;
3238
String _mime;
33-
Stream _body;
39+
HttpStatusCode _responseStatus;
40+
String _responseUrl;
41+
String _responseText;
3442

3543
#endregion
3644

@@ -45,10 +53,14 @@ public XmlHttpRequest(IWindow window)
4553
_window = window;
4654
_async = true;
4755
_method = HttpMethod.Get;
56+
_cancel = new CancellationTokenSource();
57+
_headers = new Dictionary<String, String>();
4858
_url = null;
49-
_response = null;
5059
_mime = null;
60+
_responseUrl = String.Empty;
61+
_responseText = String.Empty;
5162
_readyState = RequesterState.Unsent;
63+
_responseStatus = (HttpStatusCode)0;
5264
_credentials = false;
5365
_timeout = 45000;
5466
}
@@ -57,6 +69,14 @@ public XmlHttpRequest(IWindow window)
5769

5870
#region Properties
5971

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+
6080
/// <summary>
6181
/// Adds or removes the handler for the readystatechange event.
6282
/// </summary>
@@ -74,6 +94,11 @@ public event DomEventHandler ReadyStateChanged
7494
public RequesterState ReadyState
7595
{
7696
get { return _readyState; }
97+
private set
98+
{
99+
_readyState = value;
100+
Fire(ReadyStateChangeEvent);
101+
}
77102
}
78103

79104
/// <summary>
@@ -120,7 +145,7 @@ public XmlHttpRequestResponseType ResponseType
120145
[DomName("responseURL")]
121146
public String ResponseUrl
122147
{
123-
get { return _response != null ? _response.Address.Href : String.Empty; }
148+
get { return _responseUrl; }
124149
}
125150

126151
/// <summary>
@@ -129,7 +154,7 @@ public String ResponseUrl
129154
[DomName("status")]
130155
public Int32 StatusCode
131156
{
132-
get { return _response != null ? (Int32)_response.StatusCode : 0; }
157+
get { return (Int32)_responseStatus; }
133158
}
134159

135160
/// <summary>
@@ -138,7 +163,7 @@ public Int32 StatusCode
138163
[DomName("statusText")]
139164
public String StatusText
140165
{
141-
get { return _response != null ? _response.StatusCode.ToString() : String.Empty; }
166+
get { return StatusCode != 0 ? _responseStatus.ToString() : String.Empty; }
142167
}
143168

144169
/// <summary>
@@ -147,7 +172,7 @@ public String StatusText
147172
[DomName("response")]
148173
public Object Response
149174
{
150-
get { return _response != null ? null : String.Empty; }
175+
get { return null; }
151176
}
152177

153178
/// <summary>
@@ -156,7 +181,7 @@ public Object Response
156181
[DomName("responseText")]
157182
public String ResponseText
158183
{
159-
get { return _response != null ? null : String.Empty; }
184+
get { return _responseText; }
160185
}
161186

162187
/// <summary>
@@ -178,21 +203,11 @@ public IDocument ResponseXml
178203
[DomName("abort")]
179204
public void Abort()
180205
{
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+
}
196211
}
197212

198213
/// <summary>
@@ -204,13 +219,20 @@ public void Open(String method, String url)
204219
/// <param name="username">Should a username be used?</param>
205220
/// <param name="password">Should a password be used?</param>
206221
[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)
208223
{
209-
Open(method, url);
224+
if (_readyState == RequesterState.Unsent)
225+
{
226+
ReadyState = RequesterState.Opened;
210227

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+
}
214236
}
215237

216238
/// <summary>
@@ -220,8 +242,36 @@ public void Open(String method, String url, Boolean async, String username = nul
220242
[DomName("send")]
221243
public void Send(Object body = null)
222244
{
223-
_body = Stream.Null;
245+
if (_readyState != RequesterState.Opened)
246+
return;
224247

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+
}
225275
}
226276

227277
/// <summary>
@@ -232,7 +282,8 @@ public void Send(Object body = null)
232282
[DomName("setRequestHeader")]
233283
public void SetRequestHeader(String name, String value)
234284
{
235-
_headers[name] = value;
285+
if (_readyState == RequesterState.Opened)
286+
_headers[name] = value;
236287
}
237288

238289
/// <summary>
@@ -245,7 +296,7 @@ public String GetResponseHeader(String name)
245296
{
246297
var value = default(String);
247298

248-
if (_response != null && _response.Headers.TryGetValue(name, out value))
299+
if (HasResponseHeaders && _headers.TryGetValue(name, out value))
249300
return value;
250301

251302
return String.Empty;
@@ -258,9 +309,9 @@ public String GetResponseHeader(String name)
258309
[DomName("getAllResponseHeaders")]
259310
public String GetAllResponseHeaders()
260311
{
261-
if (_response != null)
312+
if (HasResponseHeaders)
262313
{
263-
var headers = _response.Headers;
314+
var headers = _headers;
264315
var lines = new String[headers.Count];
265316
var index = 0;
266317

@@ -280,31 +331,89 @@ public String GetAllResponseHeaders()
280331
[DomName("overrideMimeType")]
281332
public void OverrideMimeType(String mime)
282333
{
283-
_mime = mime;
334+
if (_readyState == RequesterState.Opened)
335+
_mime = mime;
284336
}
285337

286338
#endregion
287339

288-
#region Request
340+
#region Helpers
289341

290-
Url IRequest.Address
342+
async Task Receive(IDocumentLoader loader, DocumentRequest request, CancellationToken cancel)
291343
{
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+
}
293384
}
294385

295-
Stream IRequest.Content
386+
IDocumentLoader GetLoader()
296387
{
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;
298398
}
299399

300-
Dictionary<String, String> IRequest.Headers
400+
static Stream Serialize(Object body)
301401
{
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;
303411
}
304412

305-
HttpMethod IRequest.Method
413+
void Fire(String eventName)
306414
{
307-
get { return _method; }
415+
var evt = new Event(eventName);
416+
Dispatch(evt);
308417
}
309418

310419
#endregion

0 commit comments

Comments
 (0)