View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.transport.impl;
17  
18  import com.sun.net.httpserver.HttpExchange;
19  import org.fourthline.cling.model.message.Connection;
20  import org.fourthline.cling.model.message.StreamRequestMessage;
21  import org.fourthline.cling.model.message.StreamResponseMessage;
22  import org.fourthline.cling.model.message.UpnpHeaders;
23  import org.fourthline.cling.model.message.UpnpMessage;
24  import org.fourthline.cling.model.message.UpnpRequest;
25  import org.fourthline.cling.protocol.ProtocolFactory;
26  import org.fourthline.cling.transport.spi.UpnpStream;
27  import org.seamless.util.Exceptions;
28  import org.seamless.util.io.IO;
29  
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.net.HttpURLConnection;
34  import java.util.Locale;
35  import java.util.logging.Level;
36  import java.util.logging.Logger;
37  
38  /**
39   * Default implementation based on the JDK 6.0 built-in HTTP Server.
40   * <p>
41   * Instantiated by a <code>com.sun.net.httpserver.HttpHandler</code>.
42   * </p>
43   *
44   * @author Christian Bauer
45   */
46  public abstract class HttpExchangeUpnpStream extends UpnpStream {
47  
48      private static Logger log = Logger.getLogger(UpnpStream.class.getName());
49  
50      private HttpExchange httpExchange;
51  
52      public HttpExchangeUpnpStream(ProtocolFactory protocolFactory, HttpExchange httpExchange) {
53          super(protocolFactory);
54          this.httpExchange = httpExchange;
55      }
56  
57      public HttpExchange getHttpExchange() {
58          return httpExchange;
59      }
60  
61      public void run() {
62  
63          try {
64              log.fine("Processing HTTP request: " + getHttpExchange().getRequestMethod() + " " + getHttpExchange().getRequestURI());
65  
66              // Status
67              StreamRequestMessage requestMessage =
68                      new StreamRequestMessage(
69                              UpnpRequest.Method.getByHttpName(getHttpExchange().getRequestMethod()),
70                              getHttpExchange().getRequestURI()
71                      );
72  
73              if (requestMessage.getOperation().getMethod().equals(UpnpRequest.Method.UNKNOWN)) {
74                  log.fine("Method not supported by UPnP stack: " + getHttpExchange().getRequestMethod());
75                  throw new RuntimeException("Method not supported: " + getHttpExchange().getRequestMethod());
76              }
77  
78              // Protocol
79              requestMessage.getOperation().setHttpMinorVersion(
80                      getHttpExchange().getProtocol().toUpperCase(Locale.ROOT).equals("HTTP/1.1") ? 1 : 0
81              );
82  
83              log.fine("Created new request message: " + requestMessage);
84  
85              // Connection wrapper
86              requestMessage.setConnection(createConnection());
87  
88              // Headers
89              requestMessage.setHeaders(new UpnpHeaders(getHttpExchange().getRequestHeaders()));
90  
91              // Body
92              byte[] bodyBytes;
93              InputStream is = null;
94              try {
95                  is = getHttpExchange().getRequestBody();
96                  bodyBytes = IO.readBytes(is);
97              } finally {
98                  if (is != null)
99                      is.close();
100             }
101 
102             log.fine("Reading request body bytes: " + bodyBytes.length);
103 
104             if (bodyBytes.length > 0 && requestMessage.isContentTypeMissingOrText()) {
105 
106                 log.fine("Request contains textual entity body, converting then setting string on message");
107                 requestMessage.setBodyCharacters(bodyBytes);
108 
109             } else if (bodyBytes.length > 0) {
110 
111                 log.fine("Request contains binary entity body, setting bytes on message");
112                 requestMessage.setBody(UpnpMessage.BodyType.BYTES, bodyBytes);
113 
114             } else {
115                 log.fine("Request did not contain entity body");
116             }
117 
118             // Process it
119             StreamResponseMessage responseMessage = process(requestMessage);
120 
121             // Return the response
122             if (responseMessage != null) {
123                 log.fine("Preparing HTTP response message: " + responseMessage);
124 
125                 // Headers
126                 getHttpExchange().getResponseHeaders().putAll(
127                         responseMessage.getHeaders()
128                 );
129 
130                 // Body
131                 byte[] responseBodyBytes = responseMessage.hasBody() ? responseMessage.getBodyBytes() : null;
132                 int contentLength = responseBodyBytes != null ? responseBodyBytes.length : -1;
133 
134                 log.fine("Sending HTTP response message: " + responseMessage + " with content length: " + contentLength);
135                 getHttpExchange().sendResponseHeaders(responseMessage.getOperation().getStatusCode(), contentLength);
136 
137                 if (contentLength > 0) {
138                     log.fine("Response message has body, writing bytes to stream...");
139                     OutputStream os = null;
140                     try {
141                         os = getHttpExchange().getResponseBody();
142                         IO.writeBytes(os, responseBodyBytes);
143                         os.flush();
144                     } finally {
145                         if (os != null)
146                             os.close();
147                     }
148                 }
149 
150             } else {
151                 // If it's null, it's 404, everything else needs a proper httpResponse
152                 log.fine("Sending HTTP response status: " + HttpURLConnection.HTTP_NOT_FOUND);
153                 getHttpExchange().sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, -1);
154             }
155 
156             responseSent(responseMessage);
157 
158         } catch (Throwable t) {
159 
160             // You definitely want to catch all Exceptions here, otherwise the server will
161             // simply close the socket and you get an "unexpected end of file" on the client.
162             // The same is true if you just rethrow an IOException - it is a mystery why it
163             // is declared then on the HttpHandler interface if it isn't handled in any
164             // way... so we always do error handling here.
165 
166             // TODO: We should only send an error if the problem was on our side
167             // You don't have to catch Throwable unless, like we do here in unit tests,
168             // you might run into Errors as well (assertions).
169             log.fine("Exception occured during UPnP stream processing: " + t);
170             if (log.isLoggable(Level.FINE)) {
171                 log.log(Level.FINE, "Cause: " + Exceptions.unwrap(t), Exceptions.unwrap(t));
172             }
173             try {
174                 httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, -1);
175             } catch (IOException ex) {
176                 log.warning("Couldn't send error response: " + ex);
177             }
178 
179             responseException(t);
180         }
181     }
182 
183     abstract protected Connection createConnection();
184 
185 }