Mercurial > hg > ucis.core
annotate Net/HTTP.cs @ 104:327be9216006
Improved PML code
author | Ivo Smits <Ivo@UCIS.nl> |
---|---|
date | Sat, 11 Oct 2014 14:05:41 +0200 |
parents | 04c56f31db37 |
children | a03e6ad0051f |
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; } | |
6 | 102 public TCPStream TCPStream { get; private set; } |
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 } | |
6 | 414 Init(stream); |
3 | 415 } |
416 | |
6 | 417 private void Init(Stream Stream) { |
3 | 418 Writer = new StreamWriter(Stream, Encoding.ASCII); |
419 Writer.NewLine = "\r\n"; | |
420 Writer.AutoFlush = true; | |
6 | 421 Reader = new PrebufferingStream(Stream); |
422 Reader.BeginPrebuffering(PrebufferCallback, null); | |
3 | 423 } |
424 | |
75 | 425 private static String ReadLine(Stream stream) { |
3 | 426 StringBuilder s = new StringBuilder(); |
427 while (true) { | |
75 | 428 int b = stream.ReadByte(); |
3 | 429 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
|
430 if (s.Length == 0) return null; |
3 | 431 break; |
432 } 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
|
433 } else if (b == 10 || b == 0) { |
3 | 434 break; |
435 } else { | |
436 s.Append((Char)b); | |
437 } | |
438 } | |
439 return s.ToString(); | |
440 } | |
75 | 441 private String ReadLine() { |
442 return ReadLine(Reader); | |
443 } | |
3 | 444 |
6 | 445 private void PrebufferCallback(IAsyncResult ar) { |
3 | 446 State = HTTPConnectionState.ReceivingRequest; |
447 try { | |
6 | 448 Reader.EndPrebuffering(ar); |
3 | 449 String line = ReadLine(); |
450 if (line == null) { | |
451 Close(); | |
452 return; | |
453 } | |
454 if (Server.ServeFlashPolicyFile && line[0] == '<') { //<policy-file-request/> | |
455 Writer.WriteLine("<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>"); | |
6 | 456 Reader.WriteByte(0); |
3 | 457 Close(); |
458 return; | |
459 } | |
460 String[] request = line.Split(' '); | |
461 if (request.Length != 3) goto SendError400AndClose; | |
462 RequestMethod = request[0]; | |
463 String RequestAddress = request[1]; | |
464 switch (request[2]) { | |
465 case "HTTP/1.0": HTTPVersion = 10; break; | |
466 case "HTTP/1.1": HTTPVersion = 11; break; | |
75 | 467 default: goto SendError505AndClose; |
3 | 468 } |
469 request = RequestAddress.Split(new Char[] { '?' }); | |
470 RequestPath = Uri.UnescapeDataString(request[0]); | |
471 RequestQuery = request.Length > 1 ? request[1] : null; | |
472 RequestHeaders = new List<HTTPHeader>(); | |
473 while (true) { | |
474 line = ReadLine(); | |
475 if (line == null) goto SendError400AndClose; | |
476 if (line.Length == 0) break; | |
477 request = line.Split(new String[] { ": " }, 2, StringSplitOptions.None); | |
478 if (request.Length != 2) goto SendError400AndClose; | |
479 RequestHeaders.Add(new HTTPHeader(request[0], request[1])); | |
480 } | |
481 IHTTPContentProvider content = Server.ContentProvider; | |
482 if (content == null) goto SendError500AndClose; | |
483 State = HTTPConnectionState.ProcessingRequest; | |
484 content.ServeRequest(this); | |
75 | 485 Close(); |
3 | 486 } catch (Exception ex) { |
487 Console.Error.WriteLine(ex); | |
488 switch (State) { | |
489 case HTTPConnectionState.ProcessingRequest: goto SendError500AndClose; | |
490 default: | |
491 Close(); | |
492 break; | |
493 } | |
494 } | |
495 return; | |
496 | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
497 SendError400AndClose: |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
498 State = HTTPConnectionState.ProcessingRequest; |
3 | 499 SendErrorAndClose(400); |
500 return; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
501 SendError500AndClose: |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
502 State = HTTPConnectionState.ProcessingRequest; |
3 | 503 SendErrorAndClose(500); |
504 return; | |
75 | 505 SendError505AndClose: |
506 State = HTTPConnectionState.ProcessingRequest; | |
507 SendErrorAndClose(400); | |
508 return; | |
3 | 509 } |
510 | |
511 public String GetRequestHeader(String name) { | |
512 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
|
513 if (RequestHeaders == null) return null; |
3 | 514 foreach (HTTPHeader h in RequestHeaders) { |
515 if (name.Equals(h.Key, StringComparison.OrdinalIgnoreCase)) return h.Value; | |
516 } | |
517 return null; | |
518 } | |
519 public String[] GetRequestHeaders(String name) { | |
520 if (State != HTTPConnectionState.ProcessingRequest && State != HTTPConnectionState.SendingHeaders && State != HTTPConnectionState.SendingContent) throw new InvalidOperationException(); | |
521 String[] items = new String[0]; | |
100
2b5e7bb9b979
HTTP: Small fixes in server SSL support
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
522 if (RequestHeaders == null) return items; |
3 | 523 foreach (HTTPHeader h in RequestHeaders) { |
524 if (name.Equals(h.Key, StringComparison.OrdinalIgnoreCase)) ArrayUtil.Add(ref items, h.Value); | |
525 } | |
526 return items; | |
527 } | |
528 | |
75 | 529 private static String UnescapeUrlDataString(String text) { |
530 return Uri.UnescapeDataString(text.Replace('+', ' ')); | |
531 } | |
532 private static KeyValuePair<String, String>[] DecodeUrlEncodedFields(String data) { | |
533 List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); | |
534 foreach (String arg in data.Split('&')) { | |
535 String[] parts = arg.Split(new Char[] { '=' }, 2); | |
536 String key = UnescapeUrlDataString(parts[0]); | |
537 String value = (parts.Length > 1) ? UnescapeUrlDataString(parts[1]) : String.Empty; | |
538 list.Add(new KeyValuePair<string, string>(key, value)); | |
3 | 539 } |
75 | 540 return list.ToArray(); |
541 } | |
542 | |
543 public String GetQueryParameter(String name) { | |
544 foreach (KeyValuePair<String, String> kvp in GetQueryParameters()) if (kvp.Key == name) return kvp.Value; | |
545 return null; | |
546 } | |
547 public String[] GetQueryParameters(String name) { | |
548 List<String> list = new List<string>(); | |
549 foreach (KeyValuePair<String, String> kvp in GetQueryParameters()) if (kvp.Key == name) list.Add(kvp.Value); | |
550 return list.ToArray(); | |
551 } | |
552 public KeyValuePair<String, String>[] GetQueryParameters() { | |
94
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
553 if (RequestQuery == null) return new KeyValuePair<String, String>[0]; |
75 | 554 if (QueryParameters == null) QueryParameters = DecodeUrlEncodedFields(RequestQuery); |
555 return QueryParameters; | |
556 } | |
557 | |
558 public String GetPostParameter(String name) { | |
559 foreach (KeyValuePair<String, String> kvp in GetPostParameters()) if (kvp.Key == name) return kvp.Value; | |
560 return null; | |
561 } | |
562 public String[] GetPostParameters(String name) { | |
563 List<String> list = new List<string>(); | |
564 foreach (KeyValuePair<String, String> kvp in GetPostParameters()) if (kvp.Key == name) list.Add(kvp.Value); | |
565 return list.ToArray(); | |
566 } | |
567 public KeyValuePair<String, String>[] GetPostParameters() { | |
568 if (PostParameters == null) { | |
569 if (RequestMethod == "POST" && GetRequestHeader("Content-Type") == "application/x-www-form-urlencoded") { | |
570 String data; | |
571 using (StreamReader reader = new StreamReader(OpenRequestStream(), Encoding.UTF8)) data = reader.ReadToEnd(); | |
572 PostParameters = DecodeUrlEncodedFields(data); | |
573 } else { | |
574 PostParameters = new KeyValuePair<string, string>[0]; | |
575 } | |
576 } | |
577 return PostParameters; | |
578 } | |
579 | |
94
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
580 public String GetCookie(String name) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
581 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
|
582 return null; |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
583 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
584 public String[] GetCookies(String name) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
585 List<String> list = new List<string>(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
586 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
|
587 return list.ToArray(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
588 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
589 public KeyValuePair<String, String>[] GetCookies() { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
590 if (Cookies == null) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
591 String cookie = GetRequestHeader("Cookie"); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
592 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
|
593 if (cookie != null) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
594 foreach (String part in cookie.Split(';', ',')) { |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
595 String[] subparts = part.Split('='); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
596 String key = subparts[0].Trim(' ', '\t', '"'); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
597 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
|
598 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
|
599 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
600 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
601 Cookies = list.ToArray(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
602 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
603 return Cookies; |
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 |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
606 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
|
607 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
|
608 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
609 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
|
610 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
|
611 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
612 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
|
613 StringBuilder sb = new StringBuilder(); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
614 sb.Append(name); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
615 sb.Append("="); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
616 sb.Append(value); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
617 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
|
618 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
|
619 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
|
620 if (secure) sb.Append("; Secure"); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
621 if (httponly) sb.Append("; HttpOnly"); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
622 SendHeader("Set-Cookie", sb.ToString()); |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
623 } |
3c1bba376dca
HTTP: Added support for cookies, improved prefix selector
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
624 |
75 | 625 public Stream OpenRequestStream() { |
626 if (RequestStream == null) RequestStream = new HTTPInputStream(this); | |
627 return RequestStream; | |
628 } | |
629 | |
630 private static String GetMessageForStatus(int code) { | |
631 switch (code) { | |
632 case 101: return "Switching Protocols"; | |
633 case 200: return "OK"; | |
634 case 301: return "Moved Permanently"; | |
635 case 302: return "Found"; | |
636 case 303: return "See Other"; | |
637 case 304: return "Not Modified"; | |
638 case 307: return "Temporary Redirect"; | |
639 case 400: return "Bad Request"; | |
640 case 401: return "Access denied"; | |
641 case 403: return "Forbidden"; | |
642 case 404: return "Not Found"; | |
643 case 500: return "Internal Server Error"; | |
644 case 505: return "HTTP Version Not Supported"; | |
645 default: return "Unknown Status"; | |
646 } | |
3 | 647 } |
648 | |
649 public void SendStatus(int code) { | |
75 | 650 String message = GetMessageForStatus(code); |
3 | 651 SendStatus(code, message); |
652 } | |
653 public void SendStatus(int code, String message) { | |
654 if (State != HTTPConnectionState.ProcessingRequest) throw new InvalidOperationException(); | |
655 StringBuilder sb = new StringBuilder(); | |
656 sb.Append("HTTP/"); | |
657 switch (HTTPVersion) { | |
658 case 10: sb.Append("1.0"); break; | |
659 case 11: sb.Append("1.1"); break; | |
75 | 660 default: sb.Append("1.0"); break; |
3 | 661 } |
662 sb.Append(" "); | |
663 sb.Append(code); | |
664 sb.Append(" "); | |
665 sb.Append(message); | |
666 Writer.WriteLine(sb.ToString()); | |
667 State = HTTPConnectionState.SendingHeaders; | |
668 if (!SuppressStandardHeaders) { | |
669 SendHeader("Expires", "Expires: Sun, 1 Jan 2000 00:00:00 GMT"); | |
670 SendHeader("Cache-Control", "no-store, no-cache, must-revalidate"); | |
671 SendHeader("Cache-Control", "post-check=0, pre-check=0"); | |
672 SendHeader("Pragma", "no-cache"); | |
75 | 673 SendHeader("Server", "UCIS Embedded Webserver"); |
3 | 674 SendHeader("Connection", "Close"); |
675 } | |
676 } | |
677 public void SendHeader(String name, String value) { | |
678 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); | |
679 if (State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException(); | |
680 Writer.WriteLine(name + ": " + value); | |
681 } | |
75 | 682 public void SendErrorResponse(int state) { |
683 String message = GetMessageForStatus(state); | |
684 try { | |
685 SendStatus(state, message); | |
686 SendHeader("Content-Type", "text/plain"); | |
687 WriteResponseData(Encoding.ASCII.GetBytes(String.Format("Error {0}: {1}", state, message))); | |
688 } catch (Exception ex) { | |
689 Console.Error.WriteLine(ex); | |
690 } | |
691 } | |
692 public Stream OpenResponseStream(HTTPResponseStreamMode mode) { | |
693 if (ResponseStream != null) throw new InvalidOperationException("The response stream has already been opened"); | |
694 return ResponseStream = new HTTPOutputStream(this, mode); | |
695 } | |
696 public Stream OpenResponseStream(long length) { | |
697 if (ResponseStream != null) throw new InvalidOperationException("The response stream has already been opened"); | |
698 if (length < 0) throw new ArgumentException("Response length can not be negative", "length"); | |
699 return ResponseStream = new HTTPOutputStream(this, HTTPResponseStreamMode.Direct, length); | |
700 } | |
701 public void WriteResponseData(Byte[] buffer) { | |
702 WriteResponseData(buffer, 0, buffer.Length); | |
703 } | |
704 public void WriteResponseData(Byte[] buffer, int offset, int count) { | |
705 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
706 SendHeader("Content-Length", count.ToString()); | |
707 Stream stream = BeginResponseData(); | |
708 stream.Write(buffer, offset, count); | |
709 EndResponseData(); | |
710 } | |
711 private Stream BeginResponseData() { | |
3 | 712 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); |
713 if (State == HTTPConnectionState.SendingHeaders) { | |
714 Writer.WriteLine(); | |
715 State = HTTPConnectionState.SendingContent; | |
716 } | |
75 | 717 if (State != HTTPConnectionState.SendingContent) throw new InvalidOperationException("The response stream can not be opened in the current state"); |
718 return Reader; | |
719 } | |
720 private void EndResponseData() { | |
721 if (State == HTTPConnectionState.Completed || State == HTTPConnectionState.Closed) return; | |
722 OpenRequestStream().Close(); | |
723 if (State != HTTPConnectionState.SendingContent) WriteResponseData(new Byte[0]); | |
724 State = HTTPConnectionState.Completed; | |
725 } | |
726 | |
727 public Stream GetDirectStream() { | |
728 if (State == HTTPConnectionState.Closed) throw new InvalidOperationException("The context has been closed"); | |
729 BeginResponseData(); | |
730 State = HTTPConnectionState.Closed; | |
6 | 731 return Reader; |
3 | 732 } |
733 | |
75 | 734 private void SendErrorAndClose(int code) { |
99
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
735 try { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
736 SendErrorResponse(code); |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
737 } catch (IOException) { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
738 } catch (SocketException) { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
739 } catch (ObjectDisposedException) { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
740 } finally { |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
741 Close(); |
be17dd3f6927
Small fixes in HTTP server and libusb1 backend
Ivo Smits <Ivo@UCIS.nl>
parents:
95
diff
changeset
|
742 } |
75 | 743 } |
744 private void Close() { | |
3 | 745 if (State == HTTPConnectionState.Closed) return; |
6 | 746 Reader.Close(); |
3 | 747 State = HTTPConnectionState.Closed; |
748 } | |
0 | 749 } |
750 | |
3 | 751 public interface IHTTPContentProvider { |
752 void ServeRequest(HTTPContext context); | |
753 } | |
12 | 754 public delegate void HTTPContentProviderDelegate(HTTPContext context); |
755 public class HTTPContentProviderFunction : IHTTPContentProvider { | |
756 public HTTPContentProviderDelegate Handler { get; private set; } | |
757 public HTTPContentProviderFunction(HTTPContentProviderDelegate handler) { | |
758 this.Handler = handler; | |
759 } | |
760 public void ServeRequest(HTTPContext context) { | |
761 Handler(context); | |
762 } | |
763 } | |
3 | 764 public class HTTPPathSelector : IHTTPContentProvider { |
765 private List<KeyValuePair<String, IHTTPContentProvider>> Prefixes; | |
766 private StringComparison PrefixComparison; | |
767 public HTTPPathSelector() : this(false) { } | |
768 public HTTPPathSelector(Boolean caseSensitive) { | |
769 Prefixes = new List<KeyValuePair<string, IHTTPContentProvider>>(); | |
770 PrefixComparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; | |
771 } | |
772 public void AddPrefix(String prefix, IHTTPContentProvider contentProvider) { | |
773 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
|
774 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
|
775 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
|
776 }); |
3 | 777 } |
778 public void DeletePrefix(String prefix) { | |
779 Prefixes.RemoveAll(delegate(KeyValuePair<string, IHTTPContentProvider> item) { return prefix.Equals(item.Key, PrefixComparison); }); | |
780 } | |
781 public void ServeRequest(HTTPContext context) { | |
782 KeyValuePair<string, IHTTPContentProvider> c = Prefixes.Find(delegate(KeyValuePair<string, IHTTPContentProvider> item) { return context.RequestPath.StartsWith(item.Key, PrefixComparison); }); | |
783 if (c.Value != null) { | |
784 c.Value.ServeRequest(context); | |
785 } else { | |
75 | 786 context.SendErrorResponse(404); |
3 | 787 } |
788 } | |
789 } | |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
790 public class HTTPStaticContent : IHTTPContentProvider { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
791 public ArraySegment<Byte> ContentBuffer { get; set; } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
792 public String ContentType { get; set; } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
793 public HTTPStaticContent() : this(new ArraySegment<Byte>()) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
794 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
|
795 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
|
796 public HTTPStaticContent(String contentType) : this(new ArraySegment<Byte>(), contentType) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
797 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
|
798 public HTTPStaticContent(ArraySegment<Byte> content, String contentType) { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
799 this.ContentBuffer = content; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
800 this.ContentType = contentType; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
801 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
802 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
|
803 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
|
804 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
|
805 public void SetContent(String content) { SetContent(content, Encoding.UTF8); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
806 public void ServeRequest(HTTPContext context) { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
807 ArraySegment<Byte> content = ContentBuffer; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
808 if (content.Array == null) { |
75 | 809 context.SendErrorResponse(404); |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
810 return; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
811 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
812 String contentType = ContentType; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
813 context.SendStatus(200); |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
814 if (contentType != null) context.SendHeader("Content-Type", contentType); |
75 | 815 context.WriteResponseData(content.Array, content.Offset, content.Count); |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
816 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
817 } |
3 | 818 public class HTTPFileProvider : IHTTPContentProvider { |
819 public String FileName { get; private set; } | |
820 public String ContentType { get; private set; } | |
821 public HTTPFileProvider(String fileName) : this(fileName, "application/octet-stream") { } | |
822 public HTTPFileProvider(String fileName, String contentType) { | |
823 this.FileName = fileName; | |
824 this.ContentType = contentType; | |
825 } | |
826 public void ServeRequest(HTTPContext context) { | |
827 if (File.Exists(FileName)) { | |
828 using (FileStream fs = File.OpenRead(FileName)) { | |
829 context.SendStatus(200); | |
830 context.SendHeader("Content-Type", ContentType); | |
831 long left = fs.Length; | |
75 | 832 Stream response = context.OpenResponseStream(fs.Length); |
3 | 833 byte[] buffer = new byte[1024 * 10]; |
834 while (fs.CanRead) { | |
835 int len = fs.Read(buffer, 0, buffer.Length); | |
836 if (len <= 0) break; | |
837 left -= len; | |
838 response.Write(buffer, 0, len); | |
839 } | |
840 response.Close(); | |
841 } | |
842 } else { | |
75 | 843 context.SendErrorResponse(404); |
3 | 844 } |
845 } | |
0 | 846 } |
3 | 847 public class HTTPUnTarchiveProvider : IHTTPContentProvider { |
848 public String TarFileName { get; private set; } | |
849 public HTTPUnTarchiveProvider(String tarFile) { | |
850 this.TarFileName = tarFile; | |
851 } | |
852 public void ServeRequest(HTTPContext context) { | |
853 if (!File.Exists(TarFileName)) { | |
75 | 854 context.SendErrorResponse(404); |
3 | 855 return; |
856 } | |
857 String reqname1 = context.RequestPath; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
858 if (reqname1.StartsWith("/")) reqname1 = reqname1.Substring(1); |
3 | 859 String reqname2 = reqname1; |
860 if (reqname2.Length > 0 && !reqname2.EndsWith("/")) reqname2 += "/"; | |
861 reqname2 += "index.htm"; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
862 foreach (TarchiveEntry file in new TarchiveReader(TarFileName)) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
863 if (!file.IsFile) continue; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
864 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
|
865 context.SendStatus(200); |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
866 String ctype = null; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
867 switch (Path.GetExtension(file.Name).ToLowerInvariant()) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
868 case ".txt": ctype = "text/plain"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
869 case ".htm": |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
870 case ".html": ctype = "text/html"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
871 case ".css": ctype = "text/css"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
872 case ".js": ctype = "application/x-javascript"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
873 case ".png": ctype = "image/png"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
874 case ".jpg": |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
875 case ".jpeg": ctype = "image/jpeg"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
876 case ".gif": ctype = "image/gif"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
877 case ".ico": ctype = "image/x-icon"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
878 } |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
879 if (ctype != null) context.SendHeader("Content-Type", ctype); |
75 | 880 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
|
881 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
|
882 while (source.CanRead) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
883 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
|
884 if (len <= 0) break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
885 response.Write(buffer, 0, len); |
3 | 886 } |
887 } | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
888 return; |
3 | 889 } |
75 | 890 context.SendErrorResponse(404); |
3 | 891 } |
0 | 892 } |
893 } |