View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.client.io;
20  
21  import java.nio.ByteBuffer;
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  
25  import org.eclipse.jetty.util.BufferUtil;
26  import org.eclipse.jetty.util.StringUtil;
27  import org.eclipse.jetty.util.Utf8LineParser;
28  import org.eclipse.jetty.websocket.client.ClientUpgradeResponse;
29  
30  /**
31   * Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object.
32   */
33  public class HttpResponseHeaderParser
34  {
35      @SuppressWarnings("serial")
36      public static class ParseException extends RuntimeException
37      {
38          public ParseException(String message)
39          {
40              super(message);
41          }
42  
43          public ParseException(String message, Throwable cause)
44          {
45              super(message,cause);
46          }
47      }
48  
49      private enum State
50      {
51          STATUS_LINE,
52          HEADER,
53          END
54      }
55  
56      private static final Pattern PAT_HEADER = Pattern.compile("([^:]+):\\s*(.*)");
57      private static final Pattern PAT_STATUS_LINE = Pattern.compile("^HTTP/1.[01]\\s+(\\d+)\\s+(.*)",Pattern.CASE_INSENSITIVE);
58  
59      private ClientUpgradeResponse response;
60      private Utf8LineParser lineParser;
61      private State state;
62  
63      public HttpResponseHeaderParser()
64      {
65          this.lineParser = new Utf8LineParser();
66          this.state = State.STATUS_LINE;
67      }
68  
69      public boolean isDone()
70      {
71          return (state == State.END);
72      }
73  
74      public ClientUpgradeResponse parse(ByteBuffer buf) throws ParseException
75      {
76          while (!isDone() && (buf.remaining() > 0))
77          {
78              String line = lineParser.parse(buf);
79              if (line != null)
80              {
81                  if (parseHeader(line))
82                  {
83                      // Finished parsing entire header
84                      ByteBuffer copy = ByteBuffer.allocate(buf.remaining());
85                      BufferUtil.put(buf,copy);
86                      BufferUtil.flipToFlush(copy,0);
87                      this.response.setRemainingBuffer(copy);
88                      return this.response;
89                  }
90              }
91          }
92          return null;
93      }
94  
95      private boolean parseHeader(String line) throws ParseException
96      {
97          switch (state)
98          {
99              case STATUS_LINE:
100             {
101                 this.response = new ClientUpgradeResponse();
102                 Matcher mat = PAT_STATUS_LINE.matcher(line);
103                 if (!mat.matches())
104                 {
105                     throw new ParseException("Unexpected HTTP upgrade response status line [" + line + "]");
106                 }
107 
108                 try
109                 {
110                     response.setStatusCode(Integer.parseInt(mat.group(1)));
111                 }
112                 catch (NumberFormatException e)
113                 {
114                     throw new ParseException("Unexpected HTTP upgrade response status code",e);
115                 }
116                 response.setStatusReason(mat.group(2));
117                 state = State.HEADER;
118                 break;
119             }
120             case HEADER:
121             {
122                 if (StringUtil.isBlank(line))
123                 {
124                     state = State.END;
125                     return parseHeader(line);
126                 }
127 
128                 Matcher header = PAT_HEADER.matcher(line);
129                 if (header.matches())
130                 {
131                     String headerName = header.group(1);
132                     String headerValue = header.group(2);
133                     // TODO: need to split header/value if comma delimited
134                     response.addHeader(headerName,headerValue);
135                 }
136                 break;
137             }
138             case END:
139                 state = State.STATUS_LINE;
140                 return true;
141         }
142         return false;
143     }
144 }