2009-06-12 Bill Holmes <billholmes54@gmail.com>
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels / HttpReplyChannel.cs
1 //
2 // HttpReplyChannel.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2006 Novell, Inc.  http://www.novell.com
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.Net;
32 using System.ServiceModel;
33 using System.Text;
34 using System.Threading;
35
36 namespace System.ServiceModel.Channels
37 {
38         internal class HttpSimpleReplyChannel : HttpReplyChannel
39         {
40                 HttpSimpleChannelListener<IReplyChannel> source;
41                 List<HttpListenerContext> waiting = new List<HttpListenerContext> ();
42                 EndpointAddress local_address;
43                 RequestContext reqctx;
44
45                 public HttpSimpleReplyChannel (HttpSimpleChannelListener<IReplyChannel> listener)
46                         : base (listener)
47                 {
48                         this.source = listener;
49                 }
50
51                 protected override void OnClose (TimeSpan timeout)
52                 {
53                         if (reqctx != null)
54                                 reqctx.Close ();
55                 }
56
57                 public override bool TryReceiveRequest (TimeSpan timeout, out RequestContext context)
58                 {
59                         context = null;
60                         if (waiting.Count == 0 && !WaitForRequest (timeout))
61                                 return false;
62                         HttpListenerContext ctx = null;
63                         lock (waiting) {
64                                 if (waiting.Count > 0) {
65                                         ctx = waiting [0];
66                                         waiting.RemoveAt (0);
67                                 }
68                         }
69                         if (ctx == null) 
70                                 // Though as long as this instance is used
71                                 // synchronously, it should not happen.
72                                 return false;
73
74                         // FIXME: supply maxSizeOfHeaders.
75                         int maxSizeOfHeaders = 0x10000;
76
77                         Message msg = null;
78
79                         // FIXME: our HttpConnection (under HttpListener) 
80                         // somehow breaks when the underlying connection is
81                         // reused. Remove it when it gets fixed.
82                         ctx.Response.KeepAlive = false;
83
84                         if (ctx.Request.HttpMethod == "POST") {
85                                 if (!Encoder.IsContentTypeSupported (ctx.Request.ContentType)) {
86                                         ctx.Response.StatusCode = (int) HttpStatusCode.UnsupportedMediaType;
87                                         ctx.Response.StatusDescription = String.Format (
88                                                         "Expected content-type '{0}' but got '{1}'", Encoder.ContentType, ctx.Request.ContentType);
89                                         ctx.Response.Close ();
90
91                                         return false;
92                                 }
93
94                                 msg = Encoder.ReadMessage (
95                                         ctx.Request.InputStream, maxSizeOfHeaders);
96                                 if (source.MessageEncoder.MessageVersion.Envelope == EnvelopeVersion.Soap11 ||
97                                     source.MessageEncoder.MessageVersion.Addressing == AddressingVersion.None) {
98                                         string action = GetHeaderItem (ctx.Request.Headers ["SOAPAction"]);
99                                         if (action != null)
100                                                 msg.Headers.Action = action;
101                                 }
102                         } else if (ctx.Request.HttpMethod == "GET") {
103                                 msg = Message.CreateMessage (source.MessageEncoder.MessageVersion, null);
104                         }
105                         msg.Headers.To = ctx.Request.Url;
106                         
107                         HttpRequestMessageProperty prop =
108                                 new HttpRequestMessageProperty ();
109                         prop.Method = ctx.Request.HttpMethod;
110                         prop.QueryString = ctx.Request.Url.Query;
111                         if (prop.QueryString.StartsWith ("?"))
112                                 prop.QueryString = prop.QueryString.Substring (1);
113                         // FIXME: prop.SuppressEntityBody
114                         prop.Headers.Add (ctx.Request.Headers);
115                         msg.Properties.Add (HttpRequestMessageProperty.Name, prop);
116 /*
117 MessageBuffer buf = msg.CreateBufferedCopy (0x10000);
118 msg = buf.CreateMessage ();
119 System.Xml.XmlTextWriter w = new System.Xml.XmlTextWriter (Console.Out);
120 w.Formatting = System.Xml.Formatting.Indented;
121 buf.CreateMessage ().WriteMessage (w);
122 w.Close ();
123 */
124                         context = new HttpRequestContext (this, msg, ctx);
125                         reqctx = context;
126                         return true;
127                 }
128
129                 AutoResetEvent wait;
130
131                 public override bool WaitForRequest (TimeSpan timeout)
132                 {
133                         if (wait != null)
134                                 throw new InvalidOperationException ("Another wait operation is in progress");
135                         try {
136                                 wait = new AutoResetEvent (false);
137                                 source.Http.BeginGetContext (HttpContextReceived, null);
138                         } catch (HttpListenerException e) {
139                                 if (e.ErrorCode == 0x80004005) // invalid handle. Happens during shutdown.
140                                         while (true) Thread.Sleep (1000); // thread is about to be terminated.
141                                 throw;
142                         } catch (ObjectDisposedException) { return false; }
143                         return wait.WaitOne (timeout, false);
144                 }
145
146                 void HttpContextReceived (IAsyncResult result)
147                 {
148                         if (State == CommunicationState.Closing || State == CommunicationState.Closed)
149                                 return;
150                         if (wait == null)
151                                 throw new InvalidOperationException ("WaitForRequest operation has not started");
152                         waiting.Add (source.Http.EndGetContext (result));
153                         wait.Set ();
154                         wait = null;
155                 }
156         }
157
158         internal abstract class HttpReplyChannel : ReplyChannelBase
159         {
160                 HttpChannelListenerBase<IReplyChannel> source;
161                 List<HttpListenerContext> waiting = new List<HttpListenerContext> ();
162                 EndpointAddress local_address;
163
164                 public HttpReplyChannel (HttpChannelListenerBase<IReplyChannel> listener)
165                         : base (listener)
166                 {
167                         this.source = listener;
168                 }
169
170                 public MessageEncoder Encoder {
171                         get { return source.MessageEncoder; }
172                 }
173
174                 // FIXME: where is it set?
175                 public override EndpointAddress LocalAddress {
176                         get { return local_address; }
177                 }
178
179                 public override RequestContext ReceiveRequest (TimeSpan timeout)
180                 {
181                         RequestContext ctx;
182                         TryReceiveRequest (timeout, out ctx);
183                         return ctx;
184                 }
185
186                 protected override void OnAbort ()
187                 {
188                         foreach (HttpListenerContext ctx in waiting)
189                                 ctx.Request.InputStream.Close ();
190                 }
191
192                 protected override void OnClose (TimeSpan timeout)
193                 {
194                         // FIXME: consider timeout
195                         foreach (HttpListenerContext ctx in waiting)
196                                 ctx.Request.InputStream.Close ();
197                 }
198
199                 protected override void OnOpen (TimeSpan timeout)
200                 {
201                 }
202
203                 protected string GetHeaderItem (string raw)
204                 {
205                         if (raw == null || raw.Length == 0)
206                                 return raw;
207                         switch (raw [0]) {
208                         case '\'':
209                         case '"':
210                                 if (raw [raw.Length - 1] == raw [0])
211                                         return raw.Substring (1, raw.Length - 2);
212                                 // FIXME: is it simply an error?
213                                 break;
214                         }
215                         return raw;
216                 }
217         }
218 }