Mercurial > hg > ucis.core
annotate Net/HTTP.cs @ 111:df53bdd49507 default tip
Merge
author | Ivo Smits <Ivo@UCIS.nl> |
---|---|
date | Fri, 07 Nov 2014 18:37:39 +0100 |
parents | 0fc3f42a8555 |
children |
rev | line source |
---|---|
0 | 1 ???using System; |
2 using System.Collections.Generic; | |
3 using System.IO; | |
3 | 4 using System.Net; |
10 | 5 using System.Net.Security; |
3 | 6 using System.Net.Sockets; |
10 | 7 using System.Security.Cryptography.X509Certificates; |
0 | 8 using System.Text; |
3 | 9 using UCIS.Util; |
10 using HTTPHeader = System.Collections.Generic.KeyValuePair<string, string>; | |
0 | 11 |
12 namespace UCIS.Net.HTTP { | |
3 | 13 public class HTTPServer : TCPServer.IModule, IDisposable { |
14 public IHTTPContentProvider ContentProvider { get; set; } | |
15 public Boolean ServeFlashPolicyFile { get; set; } | |
10 | 16 public X509Certificate SSLCertificate { get; set; } |
3 | 17 private Socket Listener = null; |
0 | 18 |
3 | 19 public HTTPServer() { } |
0 | 20 |
3 | 21 public void Listen(int port) { |
22 Listen(new IPEndPoint(IPAddress.Any, port)); | |
0 | 23 } |
24 | |
3 | 25 public void Listen(EndPoint localep) { |
26 if (Listener != null) throw new InvalidOperationException("A listener exists"); | |
27 Listener = new Socket(localep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); | |
28 Listener.Bind(localep); | |
29 Listener.Listen(5); | |
30 Listener.BeginAccept(AcceptCallback, null); | |
0 | 31 } |
32 | |
3 | 33 private void AcceptCallback(IAsyncResult ar) { |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
34 Socket socket = null; |
3 | 35 try { |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
36 socket = Listener.EndAccept(ar); |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
37 HandleClient(socket); |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
38 } catch { |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
39 if (socket != null) socket.Close(); |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
40 } |
3 | 41 try { |
42 Listener.BeginAccept(AcceptCallback, null); | |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
43 } catch { } |
0 | 44 } |
45 | |
10 | 46 private void SslAuthenticationCallback(IAsyncResult ar) { |
47 Object[] args = (Object[])ar.AsyncState; | |
48 Socket socket = (Socket)args[0]; | |
49 SslStream ssl = (SslStream)args[1]; | |
50 try { | |
51 ssl.EndAuthenticateAsServer(ar); | |
52 new HTTPContext(this, ssl, socket); | |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
53 } catch { |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
54 socket.Close(); |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
55 } |
10 | 56 } |
57 | |
3 | 58 public void Dispose() { |
59 if (Listener != null) Listener.Close(); | |
0 | 60 } |
61 | |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
62 public void HandleClient(Socket socket, Stream streamwrapper) { |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
63 if (streamwrapper == null) streamwrapper = new NetworkStream(socket, true); |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
64 if (SSLCertificate != null) { |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
65 SslStream ssl = new SslStream(streamwrapper); |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
66 ssl.BeginAuthenticateAsServer(SSLCertificate, SslAuthenticationCallback, new Object[] { socket, ssl }); |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
67 } else { |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
68 new HTTPContext(this, streamwrapper, socket); |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
69 } |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
70 } |
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
71 |
45
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
72 public void HandleClient(Socket client) { |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
73 HandleClient(client, null); |
45
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
74 } |
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
75 |
3 | 76 bool TCPServer.IModule.Accept(TCPStream stream) { |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
77 HandleClient(stream.Socket, stream); |
3 | 78 return false; |
0 | 79 } |
80 } | |
81 | |
75 | 82 public enum HTTPResponseStreamMode { |
83 None = -1, | |
84 Direct = 0, | |
85 Buffered = 1, | |
86 Chunked = 2, | |
87 Hybrid = 3, | |
88 } | |
89 | |
0 | 90 public class HTTPContext { |
3 | 91 public HTTPServer Server { get; private set; } |
92 public EndPoint LocalEndPoint { get; private set; } | |
93 public EndPoint RemoteEndPoint { get; private set; } | |
94 | |
95 public String RequestMethod { get; private set; } | |
96 public String RequestPath { get; private set; } | |
97 public String RequestQuery { get; private set; } | |
98 public int HTTPVersion { get; set; } | |
99 | |
100 public Socket Socket { get; private set; } | |
101 public Boolean SuppressStandardHeaders { get; set; } | |
109
0fc3f42a8555
Small improvements, return TCPSocket in HTTPContext if applicable
Ivo Smits <Ivo@UCIS.nl>
parents:
106
diff
changeset
|
102 public TCPStream TCPStream { get { return Reader.BaseStream as TCPStream; } } |
3 | 103 |
104 private StreamWriter Writer; | |
6 | 105 private PrebufferingStream Reader; |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
106 private List<HTTPHeader> RequestHeaders = null; |
3 | 107 private HTTPConnectionState State = HTTPConnectionState.Starting; |
94
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
108 private KeyValuePair<String, String>[] QueryParameters = null, PostParameters = null, Cookies = null; |
75 | 109 private HTTPOutputStream ResponseStream = null; |
110 private HTTPInputStream RequestStream = null; | |
10 | 111 |
3 | 112 private enum HTTPConnectionState { |
113 Starting = 0, | |
114 ReceivingRequest = 1, | |
115 ProcessingRequest = 2, | |
116 SendingHeaders = 3, | |
117 SendingContent = 4, | |
75 | 118 Completed = 5, |
119 Closed = 6, | |
120 } | |
121 | |
122 private class HTTPOutputStream : Stream { | |
123 public HTTPResponseStreamMode Mode { get; private set; } | |
124 public HTTPContext Context { get; private set; } | |
125 private Stream OutputStream = null; | |
126 private MemoryStream Buffer = null; | |
127 private long BytesWritten = 0; | |
128 private long MaxLength; | |
129 | |
130 public HTTPOutputStream(HTTPContext context, HTTPResponseStreamMode mode) : this(context, mode, -1) { } | |
131 public HTTPOutputStream(HTTPContext context, HTTPResponseStreamMode mode, long length) { | |
132 this.Context = context; | |
133 this.Mode = mode; | |
134 this.MaxLength = length; | |
135 switch (Mode) { | |
136 case HTTPResponseStreamMode.Direct: | |
137 if (MaxLength != -1) Context.SendHeader("Content-Length", MaxLength.ToString()); | |
138 OutputStream = Context.BeginResponseData(); | |
139 break; | |
140 case HTTPResponseStreamMode.Chunked: | |
141 Context.SendHeader("Transfer-Encoding", "chunked"); | |
142 OutputStream = Context.BeginResponseData(); | |
143 break; | |
144 case HTTPResponseStreamMode.Buffered: | |
145 case HTTPResponseStreamMode.Hybrid: | |
146 if (Context.State != HTTPConnectionState.ProcessingRequest && Context.State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException("The response stream can not be created in the current state"); | |
147 break; | |
148 default: throw new InvalidOperationException("Response stream mode is not supported"); | |
149 } | |
150 } | |
151 | |
152 private void WriteBuffered(byte[] buffer, int offset, int count) { | |
153 if (Buffer == null) Buffer = new MemoryStream(); | |
154 Buffer.Write(buffer, offset, count); | |
155 } | |
156 private void WriteChunked(byte[] buffer, int offset, int count) { | |
157 Byte[] lb = Encoding.ASCII.GetBytes(count.ToString("X") + "\r\n"); | |
158 OutputStream.Write(lb, 0, lb.Length); | |
159 if (count != 0) OutputStream.Write(buffer, offset, count); | |
160 OutputStream.Write(new Byte[] { (Byte)'\r', (Byte)'\n' }, 0, 2); | |
161 } | |
162 private void HybridSwitchToChunked() { | |
163 MemoryStream oldbuffer = Buffer; | |
164 Buffer = null; | |
165 Context.SendHeader("Transfer-Encoding", "chunked"); | |
166 OutputStream = Context.BeginResponseData(); | |
167 Mode = HTTPResponseStreamMode.Chunked; | |
84
146a8d224d86
Added ArrayUtil.Remove, fixed some exceptons
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
168 if (oldbuffer != null) oldbuffer.WriteTo(this); |
75 | 169 } |
170 | |
171 public override void Write(byte[] buffer, int offset, int count) { | |
172 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
173 switch (Mode) { | |
174 case HTTPResponseStreamMode.Direct: | |
175 if (MaxLength != -1 && BytesWritten + count > MaxLength) throw new InvalidOperationException("The write operation exceeds the transfer length"); | |
176 OutputStream.Write(buffer, offset, count); | |
177 BytesWritten += count; | |
178 break; | |
179 case HTTPResponseStreamMode.Buffered: | |
180 WriteBuffered(buffer, offset, count); | |
181 break; | |
182 case HTTPResponseStreamMode.Chunked: | |
183 if (count != 0) WriteChunked(buffer, offset, count); | |
184 BytesWritten += count; | |
185 break; | |
186 case HTTPResponseStreamMode.Hybrid: | |
187 if (count > 1024 || (Buffer != null && Buffer.Length + count > 1024)) { | |
188 HybridSwitchToChunked(); | |
189 if (count != 0) WriteChunked(buffer, offset, count); | |
190 } else { | |
191 WriteBuffered(buffer, offset, count); | |
192 } | |
193 break; | |
194 } | |
195 } | |
196 class CompletedAsyncResult : AsyncResultBase { | |
197 public CompletedAsyncResult(AsyncCallback callback, Object state) : base(callback, state) { | |
198 SetCompleted(true, null); | |
199 } | |
200 } | |
201 public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { | |
202 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
203 switch (Mode) { | |
204 case HTTPResponseStreamMode.Direct: | |
205 if (MaxLength != -1 && BytesWritten + count > MaxLength) throw new InvalidOperationException("The write operation exceeds the transfer length"); | |
206 BytesWritten += count; | |
207 return OutputStream.BeginWrite(buffer, offset, count, callback, state); | |
208 case HTTPResponseStreamMode.Buffered: | |
209 case HTTPResponseStreamMode.Chunked: | |
210 case HTTPResponseStreamMode.Hybrid: | |
211 Write(buffer, offset, count); | |
212 return new CompletedAsyncResult(callback, state); | |
213 default: return null; | |
214 } | |
215 } | |
216 public override void EndWrite(IAsyncResult asyncResult) { | |
217 switch (Mode) { | |
218 case HTTPResponseStreamMode.Direct: | |
219 OutputStream.EndWrite(asyncResult); | |
220 break; | |
221 } | |
222 } | |
223 public override void SetLength(long value) { | |
224 switch (Mode) { | |
225 case HTTPResponseStreamMode.Buffered: | |
226 case HTTPResponseStreamMode.Hybrid: | |
227 if (value != 0) throw new InvalidOperationException("The length can only be set to zero using this method"); | |
228 Buffer = null; | |
229 break; | |
230 default: throw new InvalidOperationException("The operation is not supported in the current mode"); | |
231 } | |
232 } | |
233 public override long Length { | |
234 get { return MaxLength == -1 ? Position : MaxLength; } | |
235 } | |
236 public override long Position { | |
237 get { return (Buffer == null) ? BytesWritten : BytesWritten + Buffer.Length; } | |
238 set { throw new NotSupportedException(); } | |
239 } | |
240 public override void Flush() { | |
241 switch (Mode) { | |
242 case HTTPResponseStreamMode.Hybrid: | |
243 HybridSwitchToChunked(); | |
244 break; | |
245 } | |
246 } | |
247 public override bool CanWrite { | |
248 get { return Context.State != HTTPConnectionState.Completed && Context.State != HTTPConnectionState.Closed && (OutputStream == null || OutputStream.CanWrite); } | |
249 } | |
250 protected override void Dispose(bool disposing) { | |
251 base.Dispose(disposing); | |
252 if (disposing) { | |
253 switch (Mode) { | |
254 case HTTPResponseStreamMode.Direct: | |
255 /*if (MaxLength != -1 && MaxLength > BytesWritten) { | |
256 long left = MaxLength - BytesWritten; | |
257 Byte[] dummy = new Byte[Math.Min(left, 1024)]; | |
258 for (; left > 0; left -= dummy.Length) OutputStream.Write(dummy, 0, (int)Math.Min(left, dummy.Length)); | |
259 }*/ | |
260 break; | |
261 case HTTPResponseStreamMode.Chunked: | |
262 WriteChunked(null, 0, 0); | |
263 Mode = HTTPResponseStreamMode.None; | |
264 break; | |
265 case HTTPResponseStreamMode.Buffered: | |
266 case HTTPResponseStreamMode.Hybrid: | |
267 long length = (Buffer == null) ? 0 : Buffer.Length; | |
268 Context.SendHeader("Content-Length", length.ToString()); | |
269 OutputStream = Context.BeginResponseData(); | |
270 if (Buffer != null) Buffer.WriteTo(OutputStream); | |
271 Buffer = null; | |
272 Mode = HTTPResponseStreamMode.None; | |
273 break; | |
274 } | |
275 Context.EndResponseData(); | |
276 if (MaxLength != -1 && MaxLength > BytesWritten) Context.Close(); | |
277 } | |
278 } | |
279 public override bool CanTimeout { | |
280 get { return OutputStream == null ? true : OutputStream.CanTimeout; } | |
281 } | |
282 public override int WriteTimeout { | |
283 get { return OutputStream == null ? 0 : OutputStream.WriteTimeout; } | |
284 set { if (OutputStream != null) OutputStream.WriteTimeout = value; } | |
285 } | |
286 | |
287 public override bool CanRead { get { return false; } } | |
288 public override bool CanSeek { get { return false; } } | |
289 | |
290 public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | |
291 public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | |
292 } | |
293 private class HTTPInputStream : Stream { | |
294 public HTTPResponseStreamMode Mode { get; private set; } | |
295 private Stream InputStream = null; | |
296 private long BytesRead = 0; | |
297 private long BytesLeft = 0; | |
298 public HTTPInputStream(HTTPContext context) { | |
299 String TransferEncoding = context.GetRequestHeader("Transfer-Encoding"); | |
300 String ContentLength = context.GetRequestHeader("Content-Length"); | |
301 InputStream = context.Reader; | |
302 if (TransferEncoding != null && TransferEncoding.StartsWith("chunked", StringComparison.InvariantCultureIgnoreCase)) { | |
303 Mode = HTTPResponseStreamMode.Chunked; | |
304 } else if (ContentLength != null) { | |
305 Mode = HTTPResponseStreamMode.Direct; | |
306 if (!long.TryParse(ContentLength, out BytesLeft)) BytesLeft = 0; | |
307 } else { | |
308 Mode = HTTPResponseStreamMode.None; | |
309 } | |
310 } | |
311 private int ReadDirect(Byte[] buffer, int offset, int count) { | |
312 if (count > BytesLeft) count = (int)BytesLeft; | |
313 if (count == 0) return 0; | |
314 int read = InputStream.Read(buffer, offset, count); | |
315 if (read >= 0) { | |
316 BytesRead += read; | |
317 BytesLeft -= read; | |
318 } | |
319 return read; | |
320 } | |
321 public override int Read(byte[] buffer, int offset, int count) { | |
322 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
323 switch (Mode) { | |
324 case HTTPResponseStreamMode.None: | |
325 return 0; | |
326 case HTTPResponseStreamMode.Direct: | |
327 return ReadDirect(buffer, offset, count); | |
328 case HTTPResponseStreamMode.Chunked: | |
329 if (BytesLeft == 0) { | |
330 String length = ReadLine(InputStream); | |
331 if (!long.TryParse(length, out BytesLeft)) BytesLeft = 0; | |
332 if (BytesLeft == 0) { | |
333 while (true) { | |
334 String line = ReadLine(InputStream); | |
335 if (line == null || line.Length == 0) break; | |
336 } | |
337 Mode = HTTPResponseStreamMode.None; | |
338 return 0; | |
339 } | |
340 } | |
341 return ReadDirect(buffer, offset, count); | |
342 default: | |
343 return 0; | |
344 } | |
345 } | |
346 class CompletedAsyncResult : AsyncResultBase { | |
347 public int Count { get; private set; } | |
348 public CompletedAsyncResult(AsyncCallback callback, Object state, int count) | |
349 : base(callback, state) { | |
350 this.Count = count; | |
351 SetCompleted(true, null); | |
352 } | |
353 } | |
354 public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { | |
355 if (BytesLeft > 0) { | |
356 if (count > BytesLeft) count = (int)BytesLeft; | |
357 if (count == 0) return new CompletedAsyncResult(callback, state, 0); | |
358 return InputStream.BeginRead(buffer, offset, count, callback, state); | |
359 } else { | |
360 int ret = Read(buffer, offset, count); | |
361 return new CompletedAsyncResult(callback, state, ret); | |
362 } | |
363 } | |
364 public override int EndRead(IAsyncResult asyncResult) { | |
365 CompletedAsyncResult car = asyncResult as CompletedAsyncResult; | |
366 if (car != null) { | |
367 return car.Count; | |
368 } else { | |
369 return InputStream.EndRead(asyncResult); | |
370 } | |
371 } | |
372 public override long Length { | |
373 get { return BytesRead + BytesLeft; } | |
374 } | |
375 public override long Position { | |
376 get { return BytesRead; } | |
377 set { throw new NotSupportedException(); } | |
378 } | |
379 public override bool CanRead { | |
380 get { return (BytesLeft > 0 || Mode == HTTPResponseStreamMode.Chunked) && InputStream.CanRead; } | |
381 } | |
382 public override bool CanTimeout { | |
383 get { return InputStream.CanTimeout; } | |
384 } | |
385 public override int ReadTimeout { | |
386 get { return InputStream.ReadTimeout; } | |
387 set { InputStream.ReadTimeout = value; } | |
388 } | |
389 protected override void Dispose(bool disposing) { | |
390 base.Dispose(disposing); | |
391 Byte[] dummy = new Byte[1024]; | |
392 while (Read(dummy, 0, dummy.Length) != 0) ; | |
393 } | |
394 | |
395 public override bool CanSeek { get { return false; } } | |
396 public override bool CanWrite { get { return false; } } | |
397 public override void Flush() { } | |
398 public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | |
399 public override void SetLength(long value) { throw new NotSupportedException(); } | |
400 public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | |
3 | 401 } |
402 | |
10 | 403 public HTTPContext(HTTPServer server, TCPStream stream) : this(server, stream, stream.Socket) { } |
404 public HTTPContext(HTTPServer server, Socket socket) : this(server, null, socket) { } | |
405 public HTTPContext(HTTPServer server, Stream stream, Socket socket) { | |
3 | 406 this.Server = server; |
10 | 407 this.Socket = socket; |
408 if (socket != null) { | |
409 this.LocalEndPoint = socket.LocalEndPoint; | |
410 this.RemoteEndPoint = socket.RemoteEndPoint; | |
45
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
411 if (socket.ProtocolType == ProtocolType.Tcp) socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); |
10 | 412 if (stream == null) stream = new NetworkStream(socket, true); |
413 } | |
109
0fc3f42a8555
Small improvements, return TCPSocket in HTTPContext if applicable
Ivo Smits <Ivo@UCIS.nl>
parents:
106
diff
changeset
|
414 Writer = new StreamWriter(stream, Encoding.ASCII); |
3 | 415 Writer.NewLine = "\r\n"; |
416 Writer.AutoFlush = true; | |
109
0fc3f42a8555
Small improvements, return TCPSocket in HTTPContext if applicable
Ivo Smits <Ivo@UCIS.nl>
parents:
106
diff
changeset
|
417 Reader = new PrebufferingStream(stream); |
6 | 418 Reader.BeginPrebuffering(PrebufferCallback, null); |
3 | 419 } |
420 | |
75 | 421 private static String ReadLine(Stream stream) { |
3 | 422 StringBuilder s = new StringBuilder(); |
423 while (true) { | |
75 | 424 int b = stream.ReadByte(); |
3 | 425 if (b == -1) { |
4
9e2e6433f57a
Small fix for HTTP Server - Flash Cross Domain Policy File support
Ivo Smits <Ivo@UCIS.nl>
parents:
3
diff
changeset
|
426 if (s.Length == 0) return null; |
3 | 427 break; |
428 } else if (b == 13) { | |
4
9e2e6433f57a
Small fix for HTTP Server - Flash Cross Domain Policy File support
Ivo Smits <Ivo@UCIS.nl>
parents:
3
diff
changeset
|
429 } else if (b == 10 || b == 0) { |
3 | 430 break; |
431 } else { | |
432 s.Append((Char)b); | |
433 } | |
434 } | |
435 return s.ToString(); | |
436 } | |
75 | 437 private String ReadLine() { |
438 return ReadLine(Reader); | |
439 } | |
3 | 440 |
6 | 441 private void PrebufferCallback(IAsyncResult ar) { |
3 | 442 State = HTTPConnectionState.ReceivingRequest; |
443 try { | |
6 | 444 Reader.EndPrebuffering(ar); |
3 | 445 String line = ReadLine(); |
446 if (line == null) { | |
447 Close(); | |
448 return; | |
449 } | |
450 if (Server.ServeFlashPolicyFile && line[0] == '<') { //<policy-file-request/> | |
451 Writer.WriteLine("<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>"); | |
6 | 452 Reader.WriteByte(0); |
3 | 453 Close(); |
454 return; | |
455 } | |
456 String[] request = line.Split(' '); | |
457 if (request.Length != 3) goto SendError400AndClose; | |
458 RequestMethod = request[0]; | |
459 String RequestAddress = request[1]; | |
460 switch (request[2]) { | |
461 case "HTTP/1.0": HTTPVersion = 10; break; | |
462 case "HTTP/1.1": HTTPVersion = 11; break; | |
75 | 463 default: goto SendError505AndClose; |
3 | 464 } |
465 request = RequestAddress.Split(new Char[] { '?' }); | |
466 RequestPath = Uri.UnescapeDataString(request[0]); | |
467 RequestQuery = request.Length > 1 ? request[1] : null; | |
468 RequestHeaders = new List<HTTPHeader>(); | |
106
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
469 String headerName = null, headerValue = null; |
3 | 470 while (true) { |
471 line = ReadLine(); | |
472 if (line == null) goto SendError400AndClose; | |
473 if (line.Length == 0) break; | |
106
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
474 if (line[0] == ' ' || line[0] == '\t') { |
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
475 headerValue += line; |
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
476 } else { |
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
477 if (headerName != null) RequestHeaders.Add(new HTTPHeader(headerName, (headerValue ?? String.Empty).TrimStart())); |
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
478 request = line.Split(new Char[] { ':' }, 2, StringSplitOptions.None); |
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
479 if (request.Length != 2) goto SendError400AndClose; |
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
480 headerName = request[0]; |
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
481 headerValue = request[1]; |
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
482 } |
3 | 483 } |
106
a03e6ad0051f
HTTP: Fixed request header handling
Ivo Smits <Ivo@UCIS.nl>
parents:
101
diff
changeset
|
484 if (headerName != null) RequestHeaders.Add(new HTTPHeader(headerName, (headerValue ?? String.Empty).TrimStart())); |
3 | 485 IHTTPContentProvider content = Server.ContentProvider; |
486 if (content == null) goto SendError500AndClose; | |
487 State = HTTPConnectionState.ProcessingRequest; | |
488 content.ServeRequest(this); | |
75 | 489 Close(); |
3 | 490 } catch (Exception ex) { |
491 Console.Error.WriteLine(ex); | |
492 switch (State) { | |
493 case HTTPConnectionState.ProcessingRequest: goto SendError500AndClose; | |
494 default: | |
495 Close(); | |
496 break; | |
497 } | |
498 } | |
499 return; | |
500 | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
501 SendError400AndClose: |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
502 State = HTTPConnectionState.ProcessingRequest; |
3 | 503 SendErrorAndClose(400); |
504 return; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
505 SendError500AndClose: |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
506 State = HTTPConnectionState.ProcessingRequest; |
3 | 507 SendErrorAndClose(500); |
508 return; | |
75 | 509 SendError505AndClose: |
510 State = HTTPConnectionState.ProcessingRequest; | |
511 SendErrorAndClose(400); | |
512 return; | |
3 | 513 } |
514 | |
515 public String GetRequestHeader(String name) { | |
516 if (State != HTTPConnectionState.ProcessingRequest && State != HTTPConnectionState.SendingHeaders && State != HTTPConnectionState.SendingContent) throw new InvalidOperationException(); | |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
517 if (RequestHeaders == null) return null; |
3 | 518 foreach (HTTPHeader h in RequestHeaders) { |
519 if (name.Equals(h.Key, StringComparison.OrdinalIgnoreCase)) return h.Value; | |
520 } | |
521 return null; | |
522 } | |
523 public String[] GetRequestHeaders(String name) { | |
524 if (State != HTTPConnectionState.ProcessingRequest && State != HTTPConnectionState.SendingHeaders && State != HTTPConnectionState.SendingContent) throw new InvalidOperationException(); | |
525 String[] items = new String[0]; | |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
526 if (RequestHeaders == null) return items; |
3 | 527 foreach (HTTPHeader h in RequestHeaders) { |
528 if (name.Equals(h.Key, StringComparison.OrdinalIgnoreCase)) ArrayUtil.Add(ref items, h.Value); | |
529 } | |
530 return items; | |
531 } | |
532 | |
75 | 533 private static String UnescapeUrlDataString(String text) { |
534 return Uri.UnescapeDataString(text.Replace('+', ' ')); | |
535 } | |
536 private static KeyValuePair<String, String>[] DecodeUrlEncodedFields(String data) { | |
537 List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); | |
538 foreach (String arg in data.Split('&')) { | |
539 String[] parts = arg.Split(new Char[] { '=' }, 2); | |
540 String key = UnescapeUrlDataString(parts[0]); | |
541 String value = (parts.Length > 1) ? UnescapeUrlDataString(parts[1]) : String.Empty; | |
542 list.Add(new KeyValuePair<string, string>(key, value)); | |
3 | 543 } |
75 | 544 return list.ToArray(); |
545 } | |
546 | |
547 public String GetQueryParameter(String name) { | |
548 foreach (KeyValuePair<String, String> kvp in GetQueryParameters()) if (kvp.Key == name) return kvp.Value; | |
549 return null; | |
550 } | |
551 public String[] GetQueryParameters(String name) { | |
552 List<String> list = new List<string>(); | |
553 foreach (KeyValuePair<String, String> kvp in GetQueryParameters()) if (kvp.Key == name) list.Add(kvp.Value); | |
554 return list.ToArray(); | |
555 } | |
556 public KeyValuePair<String, String>[] GetQueryParameters() { | |
94
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
557 if (RequestQuery == null) return new KeyValuePair<String, String>[0]; |
75 | 558 if (QueryParameters == null) QueryParameters = DecodeUrlEncodedFields(RequestQuery); |
559 return QueryParameters; | |
560 } | |
561 | |
562 public String GetPostParameter(String name) { | |
563 foreach (KeyValuePair<String, String> kvp in GetPostParameters()) if (kvp.Key == name) return kvp.Value; | |
564 return null; | |
565 } | |
566 public String[] GetPostParameters(String name) { | |
567 List<String> list = new List<string>(); | |
568 foreach (KeyValuePair<String, String> kvp in GetPostParameters()) if (kvp.Key == name) list.Add(kvp.Value); | |
569 return list.ToArray(); | |
570 } | |
571 public KeyValuePair<String, String>[] GetPostParameters() { | |
572 if (PostParameters == null) { | |
573 if (RequestMethod == "POST" && GetRequestHeader("Content-Type") == "application/x-www-form-urlencoded") { | |
574 String data; | |
575 using (StreamReader reader = new StreamReader(OpenRequestStream(), Encoding.UTF8)) data = reader.ReadToEnd(); | |
576 PostParameters = DecodeUrlEncodedFields(data); | |
577 } else { | |
578 PostParameters = new KeyValuePair<string, string>[0]; | |
579 } | |
580 } | |
581 return PostParameters; | |
582 } | |
583 | |
94
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
584 public String GetCookie(String name) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
585 foreach (KeyValuePair<String, String> kvp in GetCookies()) if (kvp.Key == name) return kvp.Value; |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
586 return null; |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
587 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
588 public String[] GetCookies(String name) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
589 List<String> list = new List<string>(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
590 foreach (KeyValuePair<String, String> kvp in GetCookies()) if (kvp.Key == name) list.Add(kvp.Value); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
591 return list.ToArray(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
592 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
593 public KeyValuePair<String, String>[] GetCookies() { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
594 if (Cookies == null) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
595 String cookie = GetRequestHeader("Cookie"); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
596 List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
597 if (cookie != null) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
598 foreach (String part in cookie.Split(';', ',')) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
599 String[] subparts = part.Split('='); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
600 String key = subparts[0].Trim(' ', '\t', '"'); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
601 String value = (subparts.Length < 2) ? null : subparts[1].Trim(' ', '\t', '"'); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
602 list.Add(new KeyValuePair<string, string>(key, value)); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
603 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
604 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
605 Cookies = list.ToArray(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
606 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
607 return Cookies; |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
608 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
609 |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
610 public void SetCookie(String name, String value) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
611 SendHeader("Set-Cookie", String.Format("{0}={1}", name, value)); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
612 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
613 public void SetCookie(String name, String value, DateTime expire) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
614 SendHeader("Set-Cookie", String.Format("{0}={1}; Expires={2:R}", name, value, expire)); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
615 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
616 public void SetCookie(String name, String value, DateTime? expire, String path, String domain, Boolean secure, Boolean httponly) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
617 StringBuilder sb = new StringBuilder(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
618 sb.Append(name); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
619 sb.Append("="); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
620 sb.Append(value); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
621 if (expire != null) sb.AppendFormat("; Expires={0:R}", expire.Value.ToUniversalTime()); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
622 if (path != null) sb.AppendFormat("; Path={0}", path); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
623 if (domain != null) sb.AppendFormat("; Domain={0}", domain); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
624 if (secure) sb.Append("; Secure"); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
625 if (httponly) sb.Append("; HttpOnly"); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
626 SendHeader("Set-Cookie", sb.ToString()); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
627 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
628 |
75 | 629 public Stream OpenRequestStream() { |
630 if (RequestStream == null) RequestStream = new HTTPInputStream(this); | |
631 return RequestStream; | |
632 } | |
633 | |
634 private static String GetMessageForStatus(int code) { | |
635 switch (code) { | |
636 case 101: return "Switching Protocols"; | |
637 case 200: return "OK"; | |
638 case 301: return "Moved Permanently"; | |
639 case 302: return "Found"; | |
640 case 303: return "See Other"; | |
641 case 304: return "Not Modified"; | |
642 case 307: return "Temporary Redirect"; | |
643 case 400: return "Bad Request"; | |
644 case 401: return "Access denied"; | |
645 case 403: return "Forbidden"; | |
646 case 404: return "Not Found"; | |
647 case 500: return "Internal Server Error"; | |
648 case 505: return "HTTP Version Not Supported"; | |
649 default: return "Unknown Status"; | |
650 } | |
3 | 651 } |
652 | |
653 public void SendStatus(int code) { | |
75 | 654 String message = GetMessageForStatus(code); |
3 | 655 SendStatus(code, message); |
656 } | |
657 public void SendStatus(int code, String message) { | |
658 if (State != HTTPConnectionState.ProcessingRequest) throw new InvalidOperationException(); | |
659 StringBuilder sb = new StringBuilder(); | |
660 sb.Append("HTTP/"); | |
661 switch (HTTPVersion) { | |
662 case 10: sb.Append("1.0"); break; | |
663 case 11: sb.Append("1.1"); break; | |
75 | 664 default: sb.Append("1.0"); break; |
3 | 665 } |
666 sb.Append(" "); | |
667 sb.Append(code); | |
668 sb.Append(" "); | |
669 sb.Append(message); | |
670 Writer.WriteLine(sb.ToString()); | |
671 State = HTTPConnectionState.SendingHeaders; | |
672 if (!SuppressStandardHeaders) { | |
673 SendHeader("Expires", "Expires: Sun, 1 Jan 2000 00:00:00 GMT"); | |
674 SendHeader("Cache-Control", "no-store, no-cache, must-revalidate"); | |
675 SendHeader("Cache-Control", "post-check=0, pre-check=0"); | |
676 SendHeader("Pragma", "no-cache"); | |
75 | 677 SendHeader("Server", "UCIS Embedded Webserver"); |
3 | 678 SendHeader("Connection", "Close"); |
679 } | |
680 } | |
681 public void SendHeader(String name, String value) { | |
682 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); | |
683 if (State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException(); | |
684 Writer.WriteLine(name + ": " + value); | |
685 } | |
75 | 686 public void SendErrorResponse(int state) { |
687 String message = GetMessageForStatus(state); | |
688 try { | |
689 SendStatus(state, message); | |
690 SendHeader("Content-Type", "text/plain"); | |
691 WriteResponseData(Encoding.ASCII.GetBytes(String.Format("Error {0}: {1}", state, message))); | |
692 } catch (Exception ex) { | |
693 Console.Error.WriteLine(ex); | |
694 } | |
695 } | |
696 public Stream OpenResponseStream(HTTPResponseStreamMode mode) { | |
697 if (ResponseStream != null) throw new InvalidOperationException("The response stream has already been opened"); | |
698 return ResponseStream = new HTTPOutputStream(this, mode); | |
699 } | |
700 public Stream OpenResponseStream(long length) { | |
701 if (ResponseStream != null) throw new InvalidOperationException("The response stream has already been opened"); | |
702 if (length < 0) throw new ArgumentException("Response length can not be negative", "length"); | |
703 return ResponseStream = new HTTPOutputStream(this, HTTPResponseStreamMode.Direct, length); | |
704 } | |
705 public void WriteResponseData(Byte[] buffer) { | |
706 WriteResponseData(buffer, 0, buffer.Length); | |
707 } | |
708 public void WriteResponseData(Byte[] buffer, int offset, int count) { | |
709 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
710 SendHeader("Content-Length", count.ToString()); | |
711 Stream stream = BeginResponseData(); | |
712 stream.Write(buffer, offset, count); | |
713 EndResponseData(); | |
714 } | |
715 private Stream BeginResponseData() { | |
3 | 716 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); |
717 if (State == HTTPConnectionState.SendingHeaders) { | |
718 Writer.WriteLine(); | |
719 State = HTTPConnectionState.SendingContent; | |
720 } | |
75 | 721 if (State != HTTPConnectionState.SendingContent) throw new InvalidOperationException("The response stream can not be opened in the current state"); |
722 return Reader; | |
723 } | |
724 private void EndResponseData() { | |
725 if (State == HTTPConnectionState.Completed || State == HTTPConnectionState.Closed) return; | |
726 OpenRequestStream().Close(); | |
727 if (State != HTTPConnectionState.SendingContent) WriteResponseData(new Byte[0]); | |
728 State = HTTPConnectionState.Completed; | |
729 } | |
730 | |
731 public Stream GetDirectStream() { | |
732 if (State == HTTPConnectionState.Closed) throw new InvalidOperationException("The context has been closed"); | |
733 BeginResponseData(); | |
734 State = HTTPConnectionState.Closed; | |
6 | 735 return Reader; |
3 | 736 } |
737 | |
75 | 738 private void SendErrorAndClose(int code) { |
99
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
739 try { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
740 SendErrorResponse(code); |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
741 } catch (IOException) { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
742 } catch (SocketException) { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
743 } catch (ObjectDisposedException) { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
744 } finally { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
745 Close(); |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
746 } |
75 | 747 } |
748 private void Close() { | |
3 | 749 if (State == HTTPConnectionState.Closed) return; |
6 | 750 Reader.Close(); |
3 | 751 State = HTTPConnectionState.Closed; |
752 } | |
0 | 753 } |
754 | |
3 | 755 public interface IHTTPContentProvider { |
756 void ServeRequest(HTTPContext context); | |
757 } | |
12 | 758 public delegate void HTTPContentProviderDelegate(HTTPContext context); |
759 public class HTTPContentProviderFunction : IHTTPContentProvider { | |
760 public HTTPContentProviderDelegate Handler { get; private set; } | |
761 public HTTPContentProviderFunction(HTTPContentProviderDelegate handler) { | |
762 this.Handler = handler; | |
763 } | |
764 public void ServeRequest(HTTPContext context) { | |
765 Handler(context); | |
766 } | |
767 } | |
3 | 768 public class HTTPPathSelector : IHTTPContentProvider { |
769 private List<KeyValuePair<String, IHTTPContentProvider>> Prefixes; | |
770 private StringComparison PrefixComparison; | |
771 public HTTPPathSelector() : this(false) { } | |
772 public HTTPPathSelector(Boolean caseSensitive) { | |
773 Prefixes = new List<KeyValuePair<string, IHTTPContentProvider>>(); | |
774 PrefixComparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; | |
775 } | |
776 public void AddPrefix(String prefix, IHTTPContentProvider contentProvider) { | |
777 Prefixes.Add(new KeyValuePair<string, IHTTPContentProvider>(prefix, contentProvider)); | |
94
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
778 Prefixes.Sort(delegate(KeyValuePair<String, IHTTPContentProvider> a, KeyValuePair<String, IHTTPContentProvider> b) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
779 return -String.CompareOrdinal(a.Key, b.Key); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
780 }); |
3 | 781 } |
782 public void DeletePrefix(String prefix) { | |
783 Prefixes.RemoveAll(delegate(KeyValuePair<string, IHTTPContentProvider> item) { return prefix.Equals(item.Key, PrefixComparison); }); | |
784 } | |
785 public void ServeRequest(HTTPContext context) { | |
786 KeyValuePair<string, IHTTPContentProvider> c = Prefixes.Find(delegate(KeyValuePair<string, IHTTPContentProvider> item) { return context.RequestPath.StartsWith(item.Key, PrefixComparison); }); | |
787 if (c.Value != null) { | |
788 c.Value.ServeRequest(context); | |
789 } else { | |
75 | 790 context.SendErrorResponse(404); |
3 | 791 } |
792 } | |
793 } | |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
794 public class HTTPStaticContent : IHTTPContentProvider { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
795 public ArraySegment<Byte> ContentBuffer { get; set; } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
796 public String ContentType { get; set; } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
797 public HTTPStaticContent() : this(new ArraySegment<Byte>()) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
798 public HTTPStaticContent(ArraySegment<Byte> content) : this(content, "application/octet-stream") { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
799 public HTTPStaticContent(String content, String contentType) : this(Encoding.UTF8.GetBytes(content), contentType) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
800 public HTTPStaticContent(String contentType) : this(new ArraySegment<Byte>(), contentType) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
801 public HTTPStaticContent(Byte[] content, String contentType) : this(new ArraySegment<Byte>(content), contentType) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
802 public HTTPStaticContent(ArraySegment<Byte> content, String contentType) { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
803 this.ContentBuffer = content; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
804 this.ContentType = contentType; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
805 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
806 public void SetContent(Byte[] bytes) { ContentBuffer = new ArraySegment<byte>(bytes); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
807 public void SetContent(Byte[] bytes, int offset, int count) { ContentBuffer = new ArraySegment<byte>(bytes, offset, count); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
808 public void SetContent(String content, Encoding encoding) { SetContent(encoding.GetBytes(content)); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
809 public void SetContent(String content) { SetContent(content, Encoding.UTF8); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
810 public void ServeRequest(HTTPContext context) { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
811 ArraySegment<Byte> content = ContentBuffer; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
812 if (content.Array == null) { |
75 | 813 context.SendErrorResponse(404); |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
814 return; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
815 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
816 String contentType = ContentType; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
817 context.SendStatus(200); |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
818 if (contentType != null) context.SendHeader("Content-Type", contentType); |
75 | 819 context.WriteResponseData(content.Array, content.Offset, content.Count); |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
820 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
821 } |
3 | 822 public class HTTPFileProvider : IHTTPContentProvider { |
823 public String FileName { get; private set; } | |
824 public String ContentType { get; private set; } | |
825 public HTTPFileProvider(String fileName) : this(fileName, "application/octet-stream") { } | |
826 public HTTPFileProvider(String fileName, String contentType) { | |
827 this.FileName = fileName; | |
828 this.ContentType = contentType; | |
829 } | |
830 public void ServeRequest(HTTPContext context) { | |
831 if (File.Exists(FileName)) { | |
832 using (FileStream fs = File.OpenRead(FileName)) { | |
833 context.SendStatus(200); | |
834 context.SendHeader("Content-Type", ContentType); | |
835 long left = fs.Length; | |
75 | 836 Stream response = context.OpenResponseStream(fs.Length); |
3 | 837 byte[] buffer = new byte[1024 * 10]; |
838 while (fs.CanRead) { | |
839 int len = fs.Read(buffer, 0, buffer.Length); | |
840 if (len <= 0) break; | |
841 left -= len; | |
842 response.Write(buffer, 0, len); | |
843 } | |
844 response.Close(); | |
845 } | |
846 } else { | |
75 | 847 context.SendErrorResponse(404); |
3 | 848 } |
849 } | |
0 | 850 } |
3 | 851 public class HTTPUnTarchiveProvider : IHTTPContentProvider { |
852 public String TarFileName { get; private set; } | |
853 public HTTPUnTarchiveProvider(String tarFile) { | |
854 this.TarFileName = tarFile; | |
855 } | |
856 public void ServeRequest(HTTPContext context) { | |
857 if (!File.Exists(TarFileName)) { | |
75 | 858 context.SendErrorResponse(404); |
3 | 859 return; |
860 } | |
861 String reqname1 = context.RequestPath; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
862 if (reqname1.StartsWith("/")) reqname1 = reqname1.Substring(1); |
3 | 863 String reqname2 = reqname1; |
864 if (reqname2.Length > 0 && !reqname2.EndsWith("/")) reqname2 += "/"; | |
865 reqname2 += "index.htm"; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
866 foreach (TarchiveEntry file in new TarchiveReader(TarFileName)) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
867 if (!file.IsFile) continue; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
868 if (!reqname1.Equals(file.Name, StringComparison.OrdinalIgnoreCase) && !reqname2.Equals(file.Name, StringComparison.OrdinalIgnoreCase)) continue; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
869 context.SendStatus(200); |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
870 String ctype = null; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
871 switch (Path.GetExtension(file.Name).ToLowerInvariant()) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
872 case ".txt": ctype = "text/plain"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
873 case ".htm": |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
874 case ".html": ctype = "text/html"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
875 case ".css": ctype = "text/css"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
876 case ".js": ctype = "application/x-javascript"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
877 case ".png": ctype = "image/png"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
878 case ".jpg": |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
879 case ".jpeg": ctype = "image/jpeg"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
880 case ".gif": ctype = "image/gif"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
881 case ".ico": ctype = "image/x-icon"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
882 } |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
883 if (ctype != null) context.SendHeader("Content-Type", ctype); |
75 | 884 using (Stream response = context.OpenResponseStream(file.Size), source = file.GetStream()) { |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
885 byte[] buffer = new byte[Math.Min(source.Length, 1024 * 10)]; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
886 while (source.CanRead) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
887 int len = source.Read(buffer, 0, buffer.Length); |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
888 if (len <= 0) break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
889 response.Write(buffer, 0, len); |
3 | 890 } |
891 } | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
892 return; |
3 | 893 } |
75 | 894 context.SendErrorResponse(404); |
3 | 895 } |
0 | 896 } |
897 } |