001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package com.noelios.restlet.http;
020:
021: import com.noelios.restlet.util.HeaderReader;
022: import org.restlet.Server;
023: import org.restlet.data.*;
024: import org.restlet.resource.InputRepresentation;
025: import org.restlet.resource.ReadableRepresentation;
026: import org.restlet.resource.Representation;
027: import org.restlet.service.ConnectorService;
028:
029: import java.io.IOException;
030: import java.io.InputStream;
031: import java.io.OutputStream;
032: import java.nio.channels.ReadableByteChannel;
033: import java.nio.channels.WritableByteChannel;
034: import java.util.logging.Level;
035: import java.util.logging.Logger;
036:
037: /**
038: * Abstract HTTP server connector call.
039: *
040: * @author Jerome Louvel (contact@noelios.com)
041: */
042: public abstract class HttpServerCall extends HttpCall {
043: /** Indicates if the "host" header was already parsed. */
044: private boolean hostParsed;
045:
046: /**
047: * Constructor.
048: *
049: * @param server
050: * The parent server connector.
051: */
052: public HttpServerCall(Server server) {
053: this (server.getLogger(), server.getAddress(), server.getPort());
054: }
055:
056: /**
057: * Constructor.
058: *
059: * @param logger
060: * The logger.
061: * @param serverAddress
062: * The server IP address.
063: * @param serverPort
064: * The server port.
065: */
066: public HttpServerCall(Logger logger, String serverAddress,
067: int serverPort) {
068: setLogger(logger);
069: setServerAddress(serverAddress);
070: setServerPort(serverPort);
071: this .hostParsed = false;
072: }
073:
074: /**
075: * Returns the request entity channel if it exists.
076: *
077: * @return The request entity channel if it exists.
078: */
079: public abstract ReadableByteChannel getRequestChannel();
080:
081: /**
082: * Returns the request entity stream if it exists.
083: *
084: * @return The request entity stream if it exists.
085: */
086: public abstract InputStream getRequestStream();
087:
088: /**
089: * Returns the response channel if it exists.
090: *
091: * @return The response channel if it exists.
092: */
093: public abstract WritableByteChannel getResponseChannel();
094:
095: /**
096: * Returns the response stream if it exists.
097: *
098: * @return The response stream if it exists.
099: */
100: public abstract OutputStream getResponseStream();
101:
102: /**
103: * Returns the request entity if available.
104: *
105: * @return The request entity if available.
106: */
107: public Representation getRequestEntity() {
108: Representation result = null;
109: InputStream requestStream = getRequestStream();
110: ReadableByteChannel requestChannel = getRequestChannel();
111:
112: if (((requestStream != null) || (requestChannel != null))) {
113: // Extract the header values
114: MediaType contentMediaType = null;
115: long contentLength = Representation.UNKNOWN_SIZE;
116:
117: if (requestStream != null) {
118: result = new InputRepresentation(requestStream,
119: contentMediaType, contentLength);
120: } else {
121: result = new ReadableRepresentation(requestChannel,
122: contentMediaType, contentLength);
123: }
124:
125: for (Parameter header : getRequestHeaders()) {
126: if (header.getName().equalsIgnoreCase(
127: HttpConstants.HEADER_CONTENT_ENCODING)) {
128: HeaderReader hr = new HeaderReader(header
129: .getValue());
130: String value = hr.readValue();
131: while (value != null) {
132: Encoding encoding = Encoding.valueOf(value);
133: if (!encoding.equals(Encoding.IDENTITY)) {
134: result.getEncodings().add(encoding);
135: }
136: value = hr.readValue();
137: }
138: } else if (header.getName().equalsIgnoreCase(
139: HttpConstants.HEADER_CONTENT_LANGUAGE)) {
140: HeaderReader hr = new HeaderReader(header
141: .getValue());
142: String value = hr.readValue();
143: while (value != null) {
144: result.getLanguages().add(
145: Language.valueOf(value));
146: value = hr.readValue();
147: }
148: } else if (header.getName().equalsIgnoreCase(
149: HttpConstants.HEADER_CONTENT_TYPE)) {
150: ContentType contentType = new ContentType(header
151: .getValue());
152: result.setMediaType(contentType.getMediaType());
153: result.setCharacterSet(contentType
154: .getCharacterSet());
155:
156: } else if (header.getName().equalsIgnoreCase(
157: HttpConstants.HEADER_CONTENT_LENGTH)) {
158: try {
159: contentLength = Long.parseLong(header
160: .getValue());
161: } catch (NumberFormatException e) {
162: contentLength = Representation.UNKNOWN_SIZE;
163: }
164: result.setSize(contentLength);
165: }
166: }
167: }
168:
169: return result;
170: }
171:
172: /**
173: * Returns the host domain name.
174: *
175: * @return The host domain name.
176: */
177: public String getHostDomain() {
178: if (!hostParsed)
179: parseHost();
180: return super .getHostDomain();
181: }
182:
183: /**
184: * Returns the host port.
185: *
186: * @return The host port.
187: */
188: public int getHostPort() {
189: if (!hostParsed)
190: parseHost();
191: return super .getHostPort();
192: }
193:
194: /**
195: * Parses the "host" header to set the server host and port properties.
196: */
197: private void parseHost() {
198: String host = getRequestHeaders().getFirstValue(
199: HttpConstants.HEADER_HOST, true);
200: if (host != null) {
201: int colonIndex = host.indexOf(':');
202:
203: if (colonIndex != -1) {
204: super .setHostDomain(host.substring(0, colonIndex));
205: super .setHostPort(Integer.valueOf(host
206: .substring(colonIndex + 1)));
207: } else {
208: super .setHostDomain(host);
209: super .setHostPort(getProtocol().getDefaultPort());
210: }
211: } else {
212: getLogger()
213: .info(
214: "Couldn't find the mandatory \"Host\" HTTP header.");
215: }
216:
217: this .hostParsed = true;
218: }
219:
220: /**
221: * Reads the HTTP request head (request line and headers).
222: *
223: * @throws IOException
224: */
225: protected void readRequestHead(InputStream headStream)
226: throws IOException {
227: StringBuilder sb = new StringBuilder();
228:
229: // Parse the request method
230: int next = headStream.read();
231: while ((next != -1) && !HttpUtils.isSpace(next)) {
232: sb.append((char) next);
233: next = headStream.read();
234: }
235:
236: if (next == -1) {
237: throw new IOException(
238: "Unable to parse the request method. End of stream reached too early.");
239: } else {
240: setMethod(sb.toString());
241: sb.delete(0, sb.length());
242:
243: // Parse the request URI
244: next = headStream.read();
245: while ((next != -1) && !HttpUtils.isSpace(next)) {
246: sb.append((char) next);
247: next = headStream.read();
248: }
249:
250: if (next == -1) {
251: throw new IOException(
252: "Unable to parse the request URI. End of stream reached too early.");
253: } else {
254: setRequestUri(sb.toString());
255: sb.delete(0, sb.length());
256:
257: // Parse the HTTP version
258: next = headStream.read();
259: while ((next != -1)
260: && !HttpUtils.isCarriageReturn(next)) {
261: sb.append((char) next);
262: next = headStream.read();
263: }
264:
265: if (next == -1) {
266: throw new IOException(
267: "Unable to parse the HTTP version. End of stream reached too early.");
268: } else {
269: next = headStream.read();
270:
271: if (HttpUtils.isLineFeed(next)) {
272: setVersion(sb.toString());
273: sb.delete(0, sb.length());
274:
275: // Parse the headers
276: Parameter header = HttpUtils.readHeader(
277: headStream, sb);
278: while (header != null) {
279: getRequestHeaders().add(header);
280: header = HttpUtils.readHeader(headStream,
281: sb);
282: }
283: } else {
284: throw new IOException(
285: "Unable to parse the HTTP version. The carriage return must be followed by a line feed.");
286: }
287: }
288: }
289: }
290: }
291:
292: /**
293: * Sends the response back to the client. Commits the status, headers and
294: * optional entity and send them over the network. The default
295: * implementation only writes the response entity on the reponse stream or
296: * channel. Subclasses will probably also copy the response headers and
297: * status.
298: *
299: * @param response
300: * The high-level response.
301: */
302: public void sendResponse(Response response) throws IOException {
303: if (response != null) {
304: writeResponseHead(response);
305: Representation entity = response.getEntity();
306:
307: if ((entity != null)
308: && !response.getRequest().getMethod().equals(
309: Method.HEAD)
310: && !response.getStatus().equals(
311: Status.SUCCESS_NO_CONTENT)
312: && !response.getStatus().equals(
313: Status.SUCCESS_RESET_CONTENT)) {
314: // Get the connector service to callback
315: ConnectorService connectorService = getConnectorService(response
316: .getRequest());
317: if (connectorService != null)
318: connectorService.beforeSend(entity);
319:
320: writeResponseBody(entity);
321:
322: if (connectorService != null)
323: connectorService.afterSend(entity);
324: }
325:
326: if (getResponseStream() != null) {
327: try {
328: getResponseStream().flush();
329: } catch (IOException ioe) {
330: // The stream was probably already closed by the
331: // connector. Probably ok, low message priority.
332: getLogger()
333: .log(
334: Level.FINE,
335: "Exception while flushing and closing the entity stream.",
336: ioe);
337: }
338: }
339: }
340: }
341:
342: /**
343: * Effectively writes the response body. The entity to write is guaranteed
344: * to be non null. Attempts to write the entity on the response channel or
345: * response stream by default.
346: *
347: * @param entity
348: * The representation to write as entity of the body.
349: * @throws IOException
350: */
351: public void writeResponseBody(Representation entity)
352: throws IOException {
353: // Send the entity to the client
354: if (getResponseChannel() != null) {
355: entity.write(getResponseChannel());
356: } else if (getResponseStream() != null) {
357: entity.write(getResponseStream());
358: }
359: }
360:
361: /**
362: * Writes the response status line and headers. Does nothing by default.
363: *
364: * @param response
365: * The response.
366: * @throws IOException
367: */
368: public void writeResponseHead(Response response) throws IOException {
369: // Do nothing by default
370: }
371:
372: /**
373: * Writes the response head to the given output stream.
374: *
375: * @param headStream
376: * The output stream to write to.
377: * @throws IOException
378: */
379: protected void writeResponseHead(OutputStream headStream)
380: throws IOException {
381: // Write the status line
382: headStream.write(getVersion().getBytes());
383: headStream.write(' ');
384: headStream.write(Integer.toString(getStatusCode()).getBytes());
385: headStream.write(' ');
386: headStream.write(getReasonPhrase().getBytes());
387: headStream.write(13); // CR
388: headStream.write(10); // LF
389:
390: // We don't support persistent connections yet
391: getResponseHeaders().set(HttpConstants.HEADER_CONNECTION,
392: "close", true);
393:
394: // Write the response headers
395: for (Parameter header : getResponseHeaders()) {
396: HttpUtils.writeHeader(header, headStream);
397: }
398:
399: // Write the end of the headers section
400: headStream.write(13); // CR
401: headStream.write(10); // LF
402: }
403:
404: }
|