Coverage Report - org.apache.xmlrpc.webserver.Connection
 
Classes in this File Line Coverage Branch Coverage Complexity
Connection
64% 
79% 
3,538
 
 1  175
 /*
 2  
  * Copyright 1999,2005 The Apache Software Foundation.
 3  
  * 
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  * 
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  * 
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.apache.xmlrpc.webserver;
 17  
 
 18  
 import java.io.BufferedInputStream;
 19  
 import java.io.BufferedOutputStream;
 20  
 import java.io.ByteArrayOutputStream;
 21  
 import java.io.IOException;
 22  
 import java.io.InputStream;
 23  
 import java.io.OutputStream;
 24  
 import java.io.UnsupportedEncodingException;
 25  
 import java.net.Socket;
 26  
 import java.util.StringTokenizer;
 27  
 
 28  
 import org.apache.xmlrpc.common.XmlRpcHttpRequestConfig;
 29  
 import org.apache.xmlrpc.common.XmlRpcNotAuthorizedException;
 30  
 import org.apache.xmlrpc.common.XmlRpcStreamRequestConfig;
 31  
 import org.apache.xmlrpc.server.XmlRpcHttpServerConfig;
 32  
 import org.apache.xmlrpc.server.XmlRpcStreamServer;
 33  
 import org.apache.xmlrpc.util.HttpUtil;
 34  
 import org.apache.xmlrpc.util.LimitedInputStream;
 35  
 import org.apache.xmlrpc.util.ThreadPool;
 36  
 
 37  
 
 38  
 
 39  
 /** Handler for a single clients connection. This implementation
 40  
  * is able to do HTTP keepalive. In other words, it can serve
 41  
  * multiple requests via a single, physical connection.
 42  
  */
 43  1
 public class Connection implements ThreadPool.Task {
 44  
         private static final String US_ASCII = "US-ASCII";
 45  1
     private static final byte[] ctype = toHTTPBytes("Content-Type: text/xml\r\n");
 46  1
     private static final byte[] clength = toHTTPBytes("Content-Length: ");
 47  1
     private static final byte[] newline = toHTTPBytes("\r\n");
 48  1
     private static final byte[] doubleNewline = toHTTPBytes("\r\n\r\n");
 49  1
     private static final byte[] conkeep = toHTTPBytes("Connection: Keep-Alive\r\n");
 50  1
     private static final byte[] conclose = toHTTPBytes("Connection: close\r\n");
 51  1
     private static final byte[] ok = toHTTPBytes(" 200 OK\r\n");
 52  1
     private static final byte[] serverName = toHTTPBytes("Server: Apache XML-RPC 1.0\r\n");
 53  1
     private static final byte[] wwwAuthenticate = toHTTPBytes("WWW-Authenticate: Basic realm=XML-RPC\r\n");
 54  
 
 55  
         private static class BadRequestException extends IOException {
 56  
                 private static final long serialVersionUID = 3257848779234554934L;
 57  
                 BadRequestException(String pMethod) {
 58  0
                         super(pMethod);
 59  0
                 }
 60  
         }
 61  
 
 62  
         /** Returns the US-ASCII encoded byte representation of text for
 63  
      * HTTP use (as per section 2.2 of RFC 2068).
 64  
      */
 65  
     private static final byte[] toHTTPBytes(String text) {
 66  
         try {
 67  289
             return text.getBytes(US_ASCII);
 68  0
         } catch (UnsupportedEncodingException e) {
 69  0
                         throw new Error(e.getMessage() +
 70  0
                             ": HTTP requires US-ASCII encoding");
 71  
         }
 72  
     }
 73  
 
 74  
         private final WebServer webServer;
 75  
         private final Socket socket;
 76  
     private final InputStream input;
 77  
     private final OutputStream output;
 78  
         private final XmlRpcStreamServer server;
 79  
         private byte[] buffer;
 80  
 
 81  
     /** Creates a new webserver connection on the given socket.
 82  
      * @param pWebServer The webserver maintaining this connection.
 83  
      * @param pServer The server being used to execute requests.
 84  
      * @param pSocket The server socket to handle; the <code>Connection</code>
 85  
      * is responsible for closing this socket.
 86  
      * @throws IOException
 87  
      */
 88  175
     public Connection(WebServer pWebServer, XmlRpcStreamServer pServer, Socket pSocket)
 89  
                         throws IOException {
 90  175
                 webServer = pWebServer;
 91  175
                 server = pServer;
 92  175
                 socket = pSocket;
 93  
                 // set read timeout to 30 seconds
 94  175
         socket.setSoTimeout (30000);
 95  175
         input = new BufferedInputStream(socket.getInputStream()){
 96  
                     /** It may happen, that the XML parser invokes close().
 97  
                      * Closing the input stream must not occur, because
 98  
                      * that would close the whole socket. So we suppress it.
 99  
                      */
 100  
                 public void close() throws IOException {
 101  48
                 }
 102  
         };
 103  175
         output = new BufferedOutputStream(socket.getOutputStream());
 104  175
         }
 105  
 
 106  
         /** Returns the connections request configuration by
 107  
          * merging the HTTP request headers and the servers configuration.
 108  
          * @return The connections request configuration.
 109  
          * @throws IOException Reading the request headers failed.
 110  
          */
 111  
         private RequestData getRequestConfig() throws IOException {
 112  175
                 RequestData result = new RequestData(this);
 113  175
                 XmlRpcHttpServerConfig serverConfig = (XmlRpcHttpServerConfig) server.getConfig();
 114  175
                 result.setBasicEncoding(serverConfig.getBasicEncoding());
 115  175
                 result.setContentLengthOptional(serverConfig.isContentLengthOptional());
 116  175
                 result.setEnabledForExtensions(serverConfig.isEnabledForExtensions());
 117  
 
 118  
                 // reset user authentication
 119  175
                 String line = readLine();
 120  
                 // Netscape sends an extra \n\r after bodypart, swallow it
 121  175
                 if (line != null && line.length() == 0) {
 122  0
                         line = readLine();
 123  0
                         if (line == null  ||  line.length() == 0) {
 124  0
                                 return null;
 125  
                         }
 126  
             }
 127  
 
 128  
                 // tokenize first line of HTTP request
 129  175
                 StringTokenizer tokens = new StringTokenizer(line);
 130  175
                 String method = tokens.nextToken();
 131  175
                 if (!"POST".equalsIgnoreCase(method)) {
 132  0
                         throw new BadRequestException(method);
 133  
                 }
 134  175
                 result.setMethod(method);
 135  175
                 tokens.nextToken(); // Skip URI
 136  175
                 String httpVersion = tokens.nextToken();
 137  175
                 result.setHttpVersion(httpVersion);
 138  350
                 result.setKeepAlive(serverConfig.isKeepAliveEnabled()
 139  0
                                                         && WebServer.HTTP_11.equals(httpVersion));
 140  
                 do {
 141  1131
                         line = readLine();
 142  1131
                         if (line != null) {
 143  1131
                                 String lineLower = line.toLowerCase();
 144  1131
                                 if (lineLower.startsWith("content-length:")) {
 145  151
                                         String cLength = line.substring("content-length:".length());
 146  151
                                         result.setContentLength(Integer.parseInt(cLength.trim()));
 147  980
                                 } else if (lineLower.startsWith("connection:")) {
 148  140
                                         result.setKeepAlive(serverConfig.isKeepAliveEnabled()
 149  0
                                                                                 &&  lineLower.indexOf("keep-alive") > -1);
 150  910
                                 } else if (lineLower.startsWith("authorization:")) {
 151  0
                                         String credentials = line.substring("authorization:".length());
 152  0
                                         HttpUtil.parseAuthorization(result, credentials);
 153  
                                 }
 154  
                         }
 155  
                 }
 156  1131
                 while (line != null && line.length() != 0);
 157  
                 
 158  175
                 return result;
 159  
         }
 160  
 
 161  
     public void run() {
 162  
         try {
 163  175
                         for (int i = 0;  ;  i++) {
 164  175
                                 RequestData data = getRequestConfig();
 165  175
                                 if (data == null) {
 166  0
                                         break;
 167  
                                 }
 168  175
                                 server.execute(data, this);
 169  175
                                 output.flush();
 170  175
                                 if (!data.isKeepAlive()  ||  !data.isSuccess()) {
 171  0
                                         break;
 172  
                                 }
 173  
                         }
 174  0
         } catch (Throwable t) {
 175  0
                         webServer.log(t);
 176  0
         } finally {
 177  175
                         try { socket.close(); } catch (Throwable ignore) {}
 178  0
         }
 179  175
     }
 180  
 
 181  
     private String readLine() throws IOException {
 182  1306
         if (buffer == null) {
 183  175
             buffer = new byte[2048];
 184  
         }
 185  
         int next;
 186  1306
         int count = 0;
 187  32424
         for (;;) {
 188  33730
             next = input.read();
 189  33730
             if (next < 0 || next == '\n') {
 190  1306
                 break;
 191  
             }
 192  32424
             if (next != '\r') {
 193  31118
                 buffer[count++] = (byte) next;
 194  
             }
 195  32424
             if (count >= buffer.length) {
 196  0
                 throw new IOException("HTTP Header too long");
 197  
             }
 198  
         }
 199  1306
         return new String(buffer, 0, count, US_ASCII);
 200  
     }
 201  
 
 202  
     /** Returns the contents input stream.
 203  
          * @param pData The request data
 204  
          * @return The contents input stream.
 205  
          */
 206  
         public InputStream getInputStream(RequestData pData) {
 207  175
                 int contentLength = pData.getContentLength();
 208  175
                 if (contentLength == -1) {
 209  24
                         return input;
 210  
                 } else {
 211  151
                         return new LimitedInputStream(input, contentLength);
 212  
                 }
 213  
         }
 214  
 
 215  
         /** Returns the output stream for writing the response.
 216  
          * @param pConfig The request configuration.
 217  
          * @return The response output stream.
 218  
          */
 219  
         public OutputStream getOutputStream(XmlRpcStreamRequestConfig pConfig) {
 220  
                 boolean useContentLength;
 221  175
                 if (pConfig instanceof XmlRpcHttpRequestConfig) {
 222  350
                         useContentLength = !pConfig.isEnabledForExtensions()
 223  175
                                                 ||  !((XmlRpcHttpRequestConfig) pConfig).isContentLengthOptional();
 224  
                 } else {
 225  0
                         useContentLength = true;
 226  
                 }
 227  175
                 if (useContentLength) {
 228  105
                         return new ByteArrayOutputStream();
 229  
                 } else {
 230  70
                         return output;
 231  
                 }
 232  
         }
 233  
 
 234  
         /** Writes the response header and the response to the
 235  
          * output stream.
 236  
          * @param pData The request data.
 237  
          * @param pBuffer The {@link ByteArrayOutputStream} holding the response.
 238  
          * @throws IOException Writing the response failed.
 239  
          */
 240  
         public void writeResponse(RequestData pData, OutputStream pBuffer)
 241  
                         throws IOException {
 242  105
                 ByteArrayOutputStream response = (ByteArrayOutputStream) pBuffer;
 243  105
                 writeResponseHeader(pData, response.size());
 244  105
                 response.writeTo(output);
 245  105
         }
 246  
 
 247  
         /** Writes the response header to the output stream.         * 
 248  
          * @param pData The request data
 249  
          * @param pContentLength The content length, if known, or -1.
 250  
          * @throws IOException Writing the response failed.
 251  
          */
 252  
         public void writeResponseHeader(RequestData pData, int pContentLength)
 253  
                         throws IOException {
 254  175
         output.write(toHTTPBytes(pData.getHttpVersion()));
 255  175
         output.write(ok);
 256  175
         output.write(serverName);
 257  175
         output.write(pData.isKeepAlive() ? conkeep : conclose);
 258  175
         output.write(ctype);
 259  175
                 if (pContentLength != -1) {
 260  105
                 output.write(clength);
 261  105
                         output.write(toHTTPBytes(Integer.toString(pContentLength)));
 262  105
                 output.write(doubleNewline);
 263  
                 } else {
 264  70
                         output.write(newline);
 265  
                 }
 266  175
                 pData.setSuccess(true);
 267  175
         }
 268  
 
 269  
         /** Writes an error response to the output stream.
 270  
          * @param pData The request data.
 271  
          * @param pError The error being reported.
 272  
          * @param pStream The {@link ByteArrayOutputStream} with the error response.
 273  
          * @throws IOException Writing the response failed.
 274  
          */
 275  
         public void writeError(RequestData pData, Throwable pError, OutputStream pStream)
 276  
                         throws IOException {
 277  0
                 ByteArrayOutputStream errorResponse = (ByteArrayOutputStream) pStream;
 278  0
                 writeErrorHeader(pData, pError, errorResponse.size());
 279  0
                 errorResponse.writeTo(output);
 280  0
         }
 281  
 
 282  
         /** Writes an error responses headers to the output stream.
 283  
          * @param pData The request data.
 284  
          * @param pError The error being reported.
 285  
          * @param pContentLength The response length, if known, or -1.
 286  
          * @throws IOException Writing the response failed.
 287  
          */
 288  
         public void writeErrorHeader(RequestData pData, Throwable pError, int pContentLength)
 289  
                         throws IOException {
 290  0
                 if (pError instanceof BadRequestException) {
 291  0
                 output.write(toHTTPBytes(pData.getHttpVersion()));
 292  0
                 output.write(toHTTPBytes(" 400 Bad Request"));
 293  0
                 output.write(newline);
 294  0
                 output.write(serverName);
 295  0
                 output.write(doubleNewline);
 296  0
                 output.write(toHTTPBytes("Method " + pData.getMethod() +
 297  0
                                          " not implemented (try POST)"));
 298  0
                 } else if (pError instanceof XmlRpcNotAuthorizedException) {
 299  0
                 output.write(toHTTPBytes(pData.getHttpVersion()));
 300  0
                 output.write(toHTTPBytes(" 401 Unauthorized"));
 301  0
                 output.write(newline);
 302  0
                 output.write(serverName);
 303  0
                 output.write(wwwAuthenticate);
 304  0
                 output.write(doubleNewline);
 305  0
                 output.write(toHTTPBytes("Method " + pData.getMethod() + " requires a " +
 306  0
                                          "valid user name and password"));
 307  
                 } else {
 308  0
                 output.write(toHTTPBytes(pData.getHttpVersion()));
 309  0
                 output.write(ok);
 310  0
                 output.write(serverName);
 311  0
                 output.write(conclose);
 312  0
                 output.write(ctype);
 313  0
                         if (pContentLength != -1) {
 314  0
                         output.write(clength);
 315  0
                                 output.write(toHTTPBytes(Integer.toString(pContentLength)));
 316  
                         }
 317  0
                 output.write(doubleNewline);
 318  
                 }
 319  0
         }
 320  
 }