org.chromium.sdk/src/org/chromium/sdk/internal/transport/Message.java
changeset 2 e4420d2515f1
child 52 f577ea64429e
equal deleted inserted replaced
1:ef76fc2ac88c 2:e4420d2515f1
       
     1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
       
     2 // Use of this source code is governed by a BSD-style license that can be
       
     3 // found in the LICENSE file.
       
     4 
       
     5 package org.chromium.sdk.internal.transport;
       
     6 
       
     7 import java.io.BufferedReader;
       
     8 import java.io.IOException;
       
     9 import java.io.StringWriter;
       
    10 import java.io.Writer;
       
    11 import java.util.HashMap;
       
    12 import java.util.Map;
       
    13 import java.util.logging.Level;
       
    14 import java.util.logging.Logger;
       
    15 
       
    16 /**
       
    17  * A transport message encapsulating the data sent/received over the wire
       
    18  * (protocol headers and content). This class can serialize and deserialize
       
    19  * itself into a BufferedWriter according to the ChromeDevTools Protocol
       
    20  * specification.
       
    21  */
       
    22 public class Message {
       
    23 
       
    24   /**
       
    25    * This exception is thrown during Message deserialization whenever the input
       
    26    * is malformed.
       
    27    */
       
    28   public static class MalformedMessageException extends Exception {
       
    29 
       
    30     private static final long serialVersionUID = 1L;
       
    31 
       
    32     public MalformedMessageException() {
       
    33       super();
       
    34     }
       
    35 
       
    36     public MalformedMessageException(String message) {
       
    37       super(message);
       
    38     }
       
    39 
       
    40     public MalformedMessageException(Throwable cause) {
       
    41       super(cause);
       
    42     }
       
    43 
       
    44     public MalformedMessageException(String message, Throwable cause) {
       
    45       super(message, cause);
       
    46     }
       
    47 
       
    48   }
       
    49 
       
    50   /**
       
    51    * Known ChromeDevTools Protocol headers (ToolHandler implementations
       
    52    * can add their own headers.)
       
    53    */
       
    54   public enum Header {
       
    55     CONTENT_LENGTH("Content-Length"),
       
    56     TOOL("Tool"),
       
    57     DESTINATION("Destination"), ;
       
    58 
       
    59     public final String name;
       
    60 
       
    61     Header(String value) {
       
    62       this.name = value;
       
    63     }
       
    64   }
       
    65 
       
    66   /**
       
    67    * The class logger.
       
    68    */
       
    69   private static final Logger LOGGER = Logger.getLogger(Message.class.getName());
       
    70 
       
    71   /**
       
    72    * The end of protocol header line.
       
    73    */
       
    74   private static final String HEADER_TERMINATOR = "\r\n";
       
    75 
       
    76   private final HashMap<String, String> headers;
       
    77 
       
    78   private final String content;
       
    79 
       
    80   public Message(Map<String, String> headers, String content) {
       
    81     this.headers = new HashMap<String, String>(headers);
       
    82     this.content = content;
       
    83     this.headers.put(Header.CONTENT_LENGTH.name, String.valueOf(content == null
       
    84         ? 0
       
    85         : content.length()));
       
    86   }
       
    87 
       
    88   /**
       
    89    * Sends a message through the specified writer.
       
    90    *
       
    91    * @param writer to send the message through
       
    92    * @throws IOException
       
    93    */
       
    94   public void sendThrough(Writer writer) throws IOException {
       
    95     String content = maskNull(this.content);
       
    96     for (Map.Entry<String, String> entry : this.headers.entrySet()) {
       
    97       writeNonEmptyHeader(writer, entry.getKey(), entry.getValue());
       
    98     }
       
    99     writer.write(HEADER_TERMINATOR);
       
   100     if (content.length() > 0) {
       
   101       writer.write(content);
       
   102     }
       
   103     writer.flush();
       
   104   }
       
   105 
       
   106   /**
       
   107    * Reads a message from the specified reader.
       
   108    *
       
   109    * @param reader to read message from
       
   110    * @return a new message, or {@code null} if input is invalid (end-of-stream
       
   111    *         or bad message format)
       
   112    * @throws IOException
       
   113    * @throws MalformedMessageException if the input does not represent a valid
       
   114    *         message
       
   115    */
       
   116   public static Message fromBufferedReader(BufferedReader reader)
       
   117       throws IOException, MalformedMessageException {
       
   118     Map<String, String> headers = new HashMap<String, String>();
       
   119     synchronized (reader) {
       
   120       while (true) { // read headers
       
   121         String line = reader.readLine();
       
   122         if (line == null) {
       
   123           LOGGER.fine("End of stream");
       
   124           return null;
       
   125         }
       
   126         if (line.length() == 0) {
       
   127           break; // end of headers
       
   128         }
       
   129         String[] nameValue = line.split(":", 2);
       
   130         if (nameValue.length != 2) {
       
   131           LOGGER.log(Level.SEVERE, "Bad header line: {0}", line);
       
   132           return null;
       
   133         } else {
       
   134           String trimmedValue = nameValue[1].trim();
       
   135           headers.put(nameValue[0], trimmedValue);
       
   136         }
       
   137       }
       
   138 
       
   139       // Read payload if applicable
       
   140       String contentLengthStr = getHeader(headers, Header.CONTENT_LENGTH.name, "0");
       
   141       int contentLength = Integer.valueOf(contentLengthStr.trim());
       
   142       char[] content = new char[contentLength];
       
   143       int totalRead = 0;
       
   144       LOGGER.log(Level.FINER, "Reading payload: {0} bytes", contentLength);
       
   145       while (totalRead < contentLength) {
       
   146         int readBytes = reader.read(content, totalRead, contentLength - totalRead);
       
   147         if (readBytes == -1) {
       
   148           // End-of-stream (browser closed?)
       
   149           LOGGER.fine("End of stream while reading content");
       
   150           return null;
       
   151         }
       
   152         totalRead += readBytes;
       
   153       }
       
   154 
       
   155       // Construct response message
       
   156       String contentString = new String(content);
       
   157       return new Message(headers, contentString);
       
   158     }
       
   159   }
       
   160 
       
   161   /**
       
   162    * @return the "Tool" header value
       
   163    */
       
   164   public String getTool() {
       
   165     return getHeader(Header.TOOL.name, null);
       
   166   }
       
   167 
       
   168   /**
       
   169    * @return the "Destination" header value
       
   170    */
       
   171   public String getDestination() {
       
   172     return getHeader(Header.DESTINATION.name, null);
       
   173   }
       
   174 
       
   175   /**
       
   176    * @return the message content. Never {@code null} (for no content, returns an
       
   177    *         empty String)
       
   178    */
       
   179   public String getContent() {
       
   180     return content;
       
   181   }
       
   182 
       
   183   /**
       
   184    * @param name of the header
       
   185    * @param defaultValue to return if the header is not found in the message
       
   186    * @return the {@code name} header value or {@code defaultValue} if the header
       
   187    *         is not found in the message
       
   188    */
       
   189   public String getHeader(String name, String defaultValue) {
       
   190     return getHeader(this.headers, name, defaultValue);
       
   191   }
       
   192 
       
   193   private static String getHeader(Map<? extends String, String> headers, String headerName,
       
   194       String defaultValue) {
       
   195     String value = headers.get(headerName);
       
   196     if (value == null) {
       
   197       value = defaultValue;
       
   198     }
       
   199     return value;
       
   200   }
       
   201 
       
   202   private static String maskNull(String string) {
       
   203     return string == null
       
   204         ? ""
       
   205         : string;
       
   206   }
       
   207 
       
   208   private static void writeNonEmptyHeader(Writer writer, String headerName, String headerValue)
       
   209       throws IOException {
       
   210     if (headerValue != null) {
       
   211       writeHeader(writer, headerName, headerValue);
       
   212     }
       
   213   }
       
   214 
       
   215   @Override
       
   216   public String toString() {
       
   217     StringWriter sw = new StringWriter();
       
   218     try {
       
   219       this.sendThrough(sw);
       
   220     } catch (IOException e) {
       
   221       // never occurs
       
   222     }
       
   223     return sw.toString();
       
   224   }
       
   225 
       
   226   private static void writeHeader(Writer writer, String name, String value) throws IOException {
       
   227     writer.append(name).append(':').append(value).append(HEADER_TERMINATOR);
       
   228   }
       
   229 }