Merge pull request #213 from linquize/linquize-master
[mono.git] / mcs / class / System.Runtime.Remoting / System.Runtime.Remoting.Channels.Http / HttpServerTransportSink.cs
1 //
2 // HttpServerTransportSink.cs
3 // 
4 // Author:
5 //   Michael Hutchinson <mhutchinson@novell.com>
6 // 
7 // Copyright (C) 2008 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
29 using System;
30 using System.Collections;
31 using System.IO;
32 using System.Net;
33 using System.Runtime.Remoting.Messaging;
34
35 namespace System.Runtime.Remoting.Channels.Http
36 {
37         class HttpServerTransportSink : IServerChannelSink
38         {
39
40                 IServerChannelSink nextSink;
41
42                 public HttpServerTransportSink (IServerChannelSink nextSink)
43                 {
44                         this.nextSink = nextSink;
45                 }
46
47                 public IServerChannelSink NextChannelSink
48                 {
49                         get { return nextSink; }
50                 }
51
52                 public void AsyncProcessResponse (IServerResponseChannelSinkStack sinkStack, object state,
53                         IMessage msg, ITransportHeaders headers, Stream responseStream)
54                 {
55                         ContextWithId ctx = (ContextWithId) state;
56                         WriteOut (ctx.Context, headers, responseStream);
57                         ctx.Context.Response.Close ();
58                 }
59
60                 public Stream GetResponseStream (IServerResponseChannelSinkStack sinkStack, object state,
61                         IMessage msg, ITransportHeaders headers)
62                 {
63                         return null;
64                 }
65
66                 public ServerProcessing ProcessMessage (IServerChannelSinkStack sinkStack, IMessage requestMsg,
67                         ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg,
68                         out ITransportHeaders responseHeaders, out Stream responseStream)
69                 {
70                         //we know we never call this
71                         throw new NotSupportedException ();
72                 }
73
74                 public IDictionary Properties
75                 {
76                         get
77                         {
78                                 if (nextSink != null)
79                                         return nextSink.Properties;
80                                 else return null;
81                         }
82                 }
83
84                 internal void HandleRequest (HttpListenerContext context)
85                 {
86                         //build the headers
87                         ITransportHeaders requestHeaders = new TransportHeaders ();
88                         System.Collections.Specialized.NameValueCollection httpHeaders = context.Request.Headers;
89                         foreach (string key in httpHeaders.Keys) {
90                                 requestHeaders[key] = httpHeaders[key];
91                         }
92
93                         //get an ID for this connection
94                         ContextWithId identitiedContext = new ContextWithId (context);
95
96                         requestHeaders[CommonTransportKeys.RequestUri] = context.Request.Url.PathAndQuery;
97                         requestHeaders[CommonTransportKeys.IPAddress] = context.Request.RemoteEndPoint.Address;
98                         requestHeaders[CommonTransportKeys.ConnectionId] = identitiedContext.ID;
99                         requestHeaders["__RequestVerb"] = context.Request.HttpMethod;
100                         requestHeaders["__HttpVersion"] = string.Format ("HTTP/{0}.{1}",
101                                 context.Request.ProtocolVersion.Major, context.Request.ProtocolVersion.Minor);
102
103                         if (RemotingConfiguration.CustomErrorsEnabled (context.Request.IsLocal))
104                                 requestHeaders["__CustomErrorsEnabled"] = false;
105
106                         IMessage responseMsg;
107                         Stream responseStream;
108                         ITransportHeaders responseHeaders;
109                         
110                         // attach the context as state so that our async handler can use it to send the response
111                         ServerChannelSinkStack sinkStack = new ServerChannelSinkStack ();
112                         sinkStack.Push (this, identitiedContext);
113
114                         // NOTE: if we copy the InputStream before passing it so the sinks, the .NET formatters have 
115                         // unspecified internal errors. Let's hope they don't need to seek the stream!
116                         ServerProcessing proc = nextSink.ProcessMessage (sinkStack, null, requestHeaders, context.Request.InputStream, 
117                                 out responseMsg, out responseHeaders, out responseStream);
118                         
119                         switch (proc) {
120                         case ServerProcessing.Complete:
121                                 WriteOut (context, responseHeaders, responseStream);
122                                 context.Response.Close ();
123                                 break;
124
125                         case ServerProcessing.Async:
126                                 break;
127
128                         case ServerProcessing.OneWay:
129                                 context.Response.Close ();
130                                 break;
131                         }
132
133                 }
134                 
135                 internal ServerProcessing SynchronousDispatch (ITransportHeaders requestHeaders, Stream requestStream,
136                         out ITransportHeaders responseHeaders, out Stream responseStream)
137                 {
138                         IMessage responseMsg;
139                         ContextWithId identitiedContext = new ContextWithId (null);
140                         
141                         // attach the context as state so that our async handler can use it to send the response
142                         ServerChannelSinkStack sinkStack = new ServerChannelSinkStack ();
143                         sinkStack.Push (this, identitiedContext);
144                         
145                         return nextSink.ProcessMessage (sinkStack, null, requestHeaders, requestStream, 
146                                 out responseMsg, out responseHeaders, out responseStream);
147                 }
148
149                 static void WriteOut (HttpListenerContext context, ITransportHeaders responseHeaders, Stream responseStream)
150                 {
151                         //header processing taken/modified from HttpRemotingHandler
152                         if (responseHeaders != null && responseHeaders["__HttpStatusCode"] != null) {
153                                 context.Response.StatusCode = Convert.ToInt32 (responseHeaders["__HttpStatusCode"]);
154                                 context.Response.StatusDescription = (string) responseHeaders["__HttpReasonPhrase"];
155                         }
156
157                         if (responseHeaders != null) {
158                                 foreach (DictionaryEntry entry in responseHeaders) {
159                                         string key = entry.Key.ToString ();
160                                         if (key != "__HttpStatusCode" && key != "__HttpReasonPhrase") {
161                                                 context.Response.AddHeader ((string)entry.Key, responseHeaders[entry.Key].ToString ());
162                                         }
163                                 }
164                         }
165                         
166                         //we need a stream with a length, so if it's not a MemoryStream we copy it
167                         MemoryStream ms;
168                         if (responseStream is MemoryStream) {
169                                 ms = (MemoryStream) responseStream;
170                                 //this seems to be necessary for some third-party formatters
171                                 //even though my testing suggested .NET doesn't seem to seek incoming streams
172                                 ms.Position = 0;
173                         } else {
174                                 ms = new MemoryStream ();
175                                 HttpClientTransportSink.CopyStream (responseStream, ms, 1024);
176                                 ms.Position = 0;
177                                 responseStream.Close ();
178                         }
179                         
180                         //FIXME: WHY DOES CHUNKING BREAK THE TESTS?
181                         //for now, we set the content length so that the server doesn't use chunking
182                         context.Response.ContentLength64 = ms.Length;
183                         HttpClientTransportSink.CopyStream (ms, context.Response.OutputStream, 1024);
184                         ms.Close ();
185                 }
186
187                 class ContextWithId
188                 {
189                         static object lockObj = new object ();
190                         static int nextId = 0;
191
192                         HttpListenerContext context;
193                         int id;
194
195                         public HttpListenerContext Context { get { return context; } }
196                         public int ID { get { return id; } } 
197                         
198                         public ContextWithId (HttpListenerContext context)
199                         {
200                                 this.context = context;
201                                 lock (lockObj) {
202                                         //FIXME: is it really valid to roll arund and reset the ID?
203                                         unchecked {
204                                                 this.id = nextId++;
205                                         }
206                                 }
207                         }
208
209                 }
210
211         }
212 }