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 
HttpServerTransportSink(IServerChannelSink nextSink)42 		public HttpServerTransportSink (IServerChannelSink nextSink)
43 		{
44 			this.nextSink = nextSink;
45 		}
46 
47 		public IServerChannelSink NextChannelSink
48 		{
49 			get { return nextSink; }
50 		}
51 
AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, object state, IMessage msg, ITransportHeaders headers, Stream responseStream)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 
GetResponseStream(IServerResponseChannelSinkStack sinkStack, object state, IMessage msg, ITransportHeaders headers)60 		public Stream GetResponseStream (IServerResponseChannelSinkStack sinkStack, object state,
61 			IMessage msg, ITransportHeaders headers)
62 		{
63 			return null;
64 		}
65 
ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out Stream responseStream)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 
HandleRequest(HttpListenerContext context)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 
SynchronousDispatch(ITransportHeaders requestHeaders, Stream requestStream, out ITransportHeaders responseHeaders, out Stream responseStream)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 
WriteOut(HttpListenerContext context, ITransportHeaders responseHeaders, Stream responseStream)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 
ContextWithId(HttpListenerContext context)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 }
213