1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.fourthline.cling.transport.impl;
17
18 import org.fourthline.cling.model.message.Connection;
19 import org.fourthline.cling.model.message.StreamRequestMessage;
20 import org.fourthline.cling.model.message.StreamResponseMessage;
21 import org.fourthline.cling.model.message.UpnpHeaders;
22 import org.fourthline.cling.model.message.UpnpMessage;
23 import org.fourthline.cling.model.message.UpnpRequest;
24 import org.fourthline.cling.protocol.ProtocolFactory;
25 import org.fourthline.cling.transport.spi.UpnpStream;
26 import org.seamless.util.Exceptions;
27 import org.seamless.util.io.IO;
28
29 import javax.servlet.AsyncContext;
30 import javax.servlet.AsyncEvent;
31 import javax.servlet.AsyncListener;
32 import javax.servlet.ServletResponse;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.net.HttpURLConnection;
38 import java.net.URI;
39 import java.util.Enumeration;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.logging.Level;
43 import java.util.logging.Logger;
44
45
46
47
48
49
50
51
52
53
54 public abstract class AsyncServletUpnpStream extends UpnpStream implements AsyncListener {
55
56 final private static Logger log = Logger.getLogger(UpnpStream.class.getName());
57
58 final protected AsyncContext asyncContext;
59 final protected HttpServletRequest request;
60
61 protected StreamResponseMessage responseMessage;
62
63 public AsyncServletUpnpStream(ProtocolFactory protocolFactory,
64 AsyncContext asyncContext,
65 HttpServletRequest request) {
66 super(protocolFactory);
67 this.asyncContext = asyncContext;
68 this.request = request;
69 asyncContext.addListener(this);
70 }
71
72 protected HttpServletRequest getRequest() {
73 return request;
74 }
75
76 protected HttpServletResponse getResponse() {
77 ServletResponse response;
78 if ((response = asyncContext.getResponse()) == null) {
79 throw new IllegalStateException(
80 "Couldn't get response from asynchronous context, already timed out"
81 );
82 }
83 return (HttpServletResponse) response;
84 }
85
86 protected void complete() {
87 try {
88 asyncContext.complete();
89 } catch (IllegalStateException ex) {
90
91
92 log.info("Error calling servlet container's AsyncContext#complete() method: " + ex);
93 }
94 }
95
96 @Override
97 public void run() {
98 try {
99 StreamRequestMessage requestMessage = readRequestMessage();
100 if (log.isLoggable(Level.FINER))
101 log.finer("Processing new request message: " + requestMessage);
102
103 responseMessage = process(requestMessage);
104
105 if (responseMessage != null) {
106 if (log.isLoggable(Level.FINER))
107 log.finer("Preparing HTTP response message: " + responseMessage);
108 writeResponseMessage(responseMessage);
109 } else {
110
111 if (log.isLoggable(Level.FINER))
112 log.finer("Sending HTTP response status: " + HttpURLConnection.HTTP_NOT_FOUND);
113 getResponse().setStatus(HttpServletResponse.SC_NOT_FOUND);
114 }
115
116 } catch (Throwable t) {
117 log.info("Exception occurred during UPnP stream processing: " + t);
118 if (log.isLoggable(Level.FINER)) {
119 log.log(Level.FINER, "Cause: " + Exceptions.unwrap(t), Exceptions.unwrap(t));
120 }
121 if (!getResponse().isCommitted()) {
122 log.finer("Response hasn't been committed, returning INTERNAL SERVER ERROR to client");
123 getResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
124 } else {
125 log.info("Could not return INTERNAL SERVER ERROR to client, response was already committed");
126 }
127 responseException(t);
128 } finally {
129 complete();
130 }
131 }
132
133 @Override
134 public void onStartAsync(AsyncEvent event) throws IOException {
135
136
137 }
138
139 @Override
140 public void onComplete(AsyncEvent event) throws IOException {
141 if (log.isLoggable(Level.FINER))
142 log.finer("Completed asynchronous processing of HTTP request: " + event.getSuppliedRequest());
143 responseSent(responseMessage);
144 }
145
146 @Override
147 public void onTimeout(AsyncEvent event) throws IOException {
148 if (log.isLoggable(Level.FINER))
149 log.finer("Asynchronous processing of HTTP request timed out: " + event.getSuppliedRequest());
150 responseException(new Exception("Asynchronous request timed out"));
151 }
152
153 @Override
154 public void onError(AsyncEvent event) throws IOException {
155 if (log.isLoggable(Level.FINER))
156 log.finer("Asynchronous processing of HTTP request error: " + event.getThrowable());
157 responseException(event.getThrowable());
158 }
159
160 protected StreamRequestMessage readRequestMessage() throws IOException {
161
162 String requestMethod = getRequest().getMethod();
163 String requestURI = getRequest().getRequestURI();
164
165 if (log.isLoggable(Level.FINER))
166 log.finer("Processing HTTP request: " + requestMethod + " " + requestURI);
167
168 StreamRequestMessage requestMessage;
169 try {
170 requestMessage =
171 new StreamRequestMessage(
172 UpnpRequest.Method.getByHttpName(requestMethod),
173 URI.create(requestURI)
174 );
175 } catch (IllegalArgumentException ex) {
176 throw new RuntimeException("Invalid request URI: " + requestURI, ex);
177 }
178
179 if (requestMessage.getOperation().getMethod().equals(UpnpRequest.Method.UNKNOWN)) {
180 throw new RuntimeException("Method not supported: " + requestMethod);
181 }
182
183
184 requestMessage.setConnection(createConnection());
185
186
187 UpnpHeaders headers = new UpnpHeaders();
188 Enumeration<String> headerNames = getRequest().getHeaderNames();
189 while (headerNames.hasMoreElements()) {
190 String headerName = headerNames.nextElement();
191 Enumeration<String> headerValues = getRequest().getHeaders(headerName);
192 while (headerValues.hasMoreElements()) {
193 String headerValue = headerValues.nextElement();
194 headers.add(headerName, headerValue);
195 }
196 }
197 requestMessage.setHeaders(headers);
198
199
200 byte[] bodyBytes;
201 InputStream is = null;
202 try {
203 is = getRequest().getInputStream();
204 bodyBytes = IO.readBytes(is);
205 } finally {
206 if (is != null)
207 is.close();
208 }
209 if (log.isLoggable(Level.FINER))
210 log.finer("Reading request body bytes: " + bodyBytes.length);
211
212 if (bodyBytes.length > 0 && requestMessage.isContentTypeMissingOrText()) {
213
214 if (log.isLoggable(Level.FINER))
215 log.finer("Request contains textual entity body, converting then setting string on message");
216 requestMessage.setBodyCharacters(bodyBytes);
217
218 } else if (bodyBytes.length > 0) {
219
220 if (log.isLoggable(Level.FINER))
221 log.finer("Request contains binary entity body, setting bytes on message");
222 requestMessage.setBody(UpnpMessage.BodyType.BYTES, bodyBytes);
223
224 } else {
225 if (log.isLoggable(Level.FINER))
226 log.finer("Request did not contain entity body");
227 }
228
229 return requestMessage;
230 }
231
232 protected void writeResponseMessage(StreamResponseMessage responseMessage) throws IOException {
233 if (log.isLoggable(Level.FINER))
234 log.finer("Sending HTTP response status: " + responseMessage.getOperation().getStatusCode());
235
236 getResponse().setStatus(responseMessage.getOperation().getStatusCode());
237
238
239 for (Map.Entry<String, List<String>> entry : responseMessage.getHeaders().entrySet()) {
240 for (String value : entry.getValue()) {
241 getResponse().addHeader(entry.getKey(), value);
242 }
243 }
244
245 getResponse().setDateHeader("Date", System.currentTimeMillis());
246
247
248 byte[] responseBodyBytes = responseMessage.hasBody() ? responseMessage.getBodyBytes() : null;
249 int contentLength = responseBodyBytes != null ? responseBodyBytes.length : -1;
250
251 if (contentLength > 0) {
252 getResponse().setContentLength(contentLength);
253 log.finer("Response message has body, writing bytes to stream...");
254 IO.writeBytes(getResponse().getOutputStream(), responseBodyBytes);
255 }
256 }
257
258 abstract protected Connection createConnection();
259
260 }