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.common.io.http;
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  
29  /**
30   * Responsible for reading UTF8 Response Header lines and parsing them into a provided UpgradeResponse object.
31   */
32  public class HttpResponseHeaderParser
33  {
34      @SuppressWarnings("serial")
35      public static class ParseException extends RuntimeException
36      {
37          public ParseException(String message)
38          {
39              super(message);
40          }
41  
42          public ParseException(String message, Throwable cause)
43          {
44              super(message,cause);
45          }
46      }
47  
48      private enum State
49      {
50          STATUS_LINE,
51          HEADER,
52          END
53      }
54  
55      private static final Pattern PAT_HEADER = Pattern.compile("([^:]+):\\s*(.*)");
56      private static final Pattern PAT_STATUS_LINE = Pattern.compile("^HTTP/1.[01]\\s+(\\d+)\\s+(.*)",Pattern.CASE_INSENSITIVE);
57  
58      private final HttpResponseHeaderParseListener listener;
59      private final Utf8LineParser lineParser;
60      private State state;
61  
62      public HttpResponseHeaderParser(HttpResponseHeaderParseListener listener)
63      {
64          this.listener = listener;
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 HttpResponseHeaderParseListener 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.listener.setRemainingBuffer(copy);
88                      return listener;
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                 Matcher mat = PAT_STATUS_LINE.matcher(line);
102                 if (!mat.matches())
103                 {
104                     throw new ParseException("Unexpected HTTP response status line [" + line + "]");
105                 }
106 
107                 try
108                 {
109                     listener.setStatusCode(Integer.parseInt(mat.group(1)));
110                 }
111                 catch (NumberFormatException e)
112                 {
113                     throw new ParseException("Unexpected HTTP response status code",e);
114                 }
115                 listener.setStatusReason(mat.group(2));
116                 state = State.HEADER;
117                 break;
118             }
119             case HEADER:
120             {
121                 if (StringUtil.isBlank(line))
122                 {
123                     state = State.END;
124                     return parseHeader(line);
125                 }
126 
127                 Matcher header = PAT_HEADER.matcher(line);
128                 if (header.matches())
129                 {
130                     String headerName = header.group(1);
131                     String headerValue = header.group(2);
132                     // do need to split header/value if comma delimited?
133                     listener.addHeader(headerName,headerValue);
134                 }
135                 break;
136             }
137             case END:
138                 state = State.STATUS_LINE;
139                 return true;
140         }
141         return false;
142     }
143 }