View Javadoc

1   // Copyright (C) 2004 - 2010 Philip Aston
2   // All rights reserved.
3   //
4   // This file is part of The Grinder software distribution. Refer to
5   // the file LICENSE which is part of The Grinder distribution for
6   // licensing details. The Grinder distribution is available on the
7   // Internet at http://grinder.sourceforge.net/
8   //
9   // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
11  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
12  // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
13  // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
14  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
15  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
16  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
17  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
18  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
19  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
20  // OF THE POSSIBILITY OF SUCH DAMAGE.
21  
22  package net.grinder.plugin.http;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.net.ServerSocket;
28  import java.net.Socket;
29  import java.net.SocketException;
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.concurrent.atomic.AtomicBoolean;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  
36  import junit.framework.Assert;
37  import net.grinder.common.UncheckedInterruptedException;
38  import HTTPClient.NVPair;
39  
40  
41  /**
42   * Active class that accepts a connection on a socket, reads an HTTP request,
43   * and returns a response. The details of the request can then be retrieved.
44   */
45  class HTTPRequestHandler extends Assert implements Runnable {
46    private static final Pattern s_contentLengthPattern;
47  
48    private final List<NVPair> m_headers = new ArrayList<NVPair>();
49  
50    static {
51      try {
52        s_contentLengthPattern =
53          Pattern.compile("^Content-Length:[ \\t]*(.*)\\r?$",
54                          Pattern.MULTILINE |
55                          Pattern.CASE_INSENSITIVE);
56      }
57      catch (Exception e) {
58        throw new ExceptionInInitializerError(e);
59      }
60    }
61  
62    private final ServerSocket m_serverSocket;
63    private String m_lastRequestHeaders;
64    private byte[] m_lastRequestBody;
65    private String m_body;
66    private AtomicBoolean m_started = new AtomicBoolean();
67  
68    private long m_responseDelay = 0;
69  
70    public HTTPRequestHandler() throws Exception {
71      m_serverSocket = new ServerSocket(0);
72    }
73  
74    public void start() throws InterruptedException {
75      if (m_started.get()) {
76        throw new AssertionError("Already started");
77      }
78  
79      new Thread(this, getClass().getName()).start();
80  
81      while (!m_started.get()) {
82        Thread.sleep(1);
83      }
84    }
85  
86    public final void shutdown() throws Exception {
87      m_serverSocket.close();
88    }
89  
90    public final String getURL() {
91      return "http://localhost:" + m_serverSocket.getLocalPort();
92    }
93  
94    public final String getLastRequestHeaders() {
95      return m_lastRequestHeaders;
96    }
97  
98    public final byte[] getLastRequestBody() {
99      return m_lastRequestBody;
100   }
101 
102   public final String getRequestFirstHeader() {
103     final String text = getLastRequestHeaders();
104 
105     final int i = text.indexOf("\r\n");
106     assertTrue("Has at least one line", i>=0);
107     return text.substring(0, i);
108   }
109 
110   public final void assertRequestContainsHeader(String line) {
111     final String text = getLastRequestHeaders();
112 
113     int start = 0;
114     int i;
115 
116     while ((i = text.indexOf("\r\n", start)) != -1) {
117       if (text.substring(start, i).equals(line)) {
118         return;
119       }
120 
121       start = i + 2;
122     }
123 
124     if (text.substring(start).equals(line)) {
125       return;
126     }
127 
128     fail(text + " does not contain " + line);
129   }
130 
131   public final void assertRequestDoesNotContainHeader(String line) {
132     final String text = getLastRequestHeaders();
133 
134     int start = 0;
135     int i;
136 
137     while((i = text.indexOf("\r\n", start)) != -1) {
138       assertTrue(!text.substring(start, i).equals(line));
139       start = i + 2;
140     }
141 
142     assertTrue(!text.substring(start).equals(line));
143   }
144 
145   public final void run() {
146     try {
147       m_started.set(true);
148 
149       while (true) {
150         final Socket localSocket;
151 
152         try {
153           localSocket = m_serverSocket.accept();
154         }
155         catch (SocketException e) {
156           // Socket's been closed, lets quit.
157           break;
158         }
159 
160         final InputStream in = localSocket.getInputStream();
161 
162         final StringBuffer headerBuffer = new StringBuffer();
163         final byte[] buffer = new byte[1000];
164         int n;
165         int bodyStart = -1;
166 
167         READ_HEADERS:
168         while ((n = in.read(buffer, 0, buffer.length)) != -1) {
169 
170           for (int i=0; i<n-3; ++i) {
171             if (buffer[i] == '\r' &&
172                 buffer[i+1] == '\n' &&
173                 buffer[i+2] == '\r' &&
174                 buffer[i+3] == '\n') {
175 
176               headerBuffer.append(new String(buffer, 0, i));
177               bodyStart = i + 4;
178               break READ_HEADERS;
179             }
180           }
181 
182           headerBuffer.append(new String(buffer, 0, n));
183         }
184 
185         if (bodyStart == -1) {
186           throw new IOException("No header boundary");
187         }
188 
189         m_lastRequestHeaders = headerBuffer.toString();
190 
191         final Matcher matcher =
192           s_contentLengthPattern.matcher(m_lastRequestHeaders);
193 
194         if (matcher.find()) {
195           final int contentLength =
196             Integer.parseInt(matcher.group(1).trim());
197 
198           m_lastRequestBody = new byte[contentLength];
199 
200           int bodyBytes = n - bodyStart;
201 
202           System.arraycopy(buffer, bodyStart, m_lastRequestBody, 0,
203                            bodyBytes);
204 
205           while (bodyBytes < m_lastRequestBody.length) {
206             final int bytesRead =
207               in.read(m_lastRequestBody, bodyBytes,
208                       m_lastRequestBody.length - bodyBytes);
209 
210             if (bytesRead == -1) {
211               throw new IOException("Content-length too large");
212             }
213 
214             bodyBytes += bytesRead;
215           }
216 
217           if (in.available() > 0) {
218             throw new IOException("Content-length too small");
219           }
220         }
221         else {
222           m_lastRequestBody = null;
223         }
224 
225         try {
226           Thread.sleep(m_responseDelay);
227         }
228         catch (InterruptedException e) {
229           throw new UncheckedInterruptedException(e);
230         }
231 
232         final OutputStream out = localSocket.getOutputStream();
233 
234         final StringBuffer response = new StringBuffer();
235         writeHeaders(response);
236         response.append("\r\n");
237 
238         if (m_body != null) {
239           response.append(m_body);
240         }
241 
242         out.write(response.toString().getBytes());
243         out.flush();
244 
245         localSocket.close();
246       }
247     }
248     catch (IOException e) {
249       // Ignore, it might be expected The caller will have to call start()
250       // again.
251     }
252     finally {
253       try {
254         m_serverSocket.close();
255       }
256       catch (IOException e) {
257         // Whatever.
258       }
259     }
260   }
261 
262   /**
263    * Subclass HTTPRequestHandler to change these default headers.
264    */
265   protected void writeHeaders(StringBuffer response) {
266     response.append("HTTP/1.0 200 OK\r\n");
267 
268     for (NVPair pair : m_headers) {
269       response.append(pair.getName()).append(": ").append(pair.getValue());
270       response.append("\r\n");
271     }
272   }
273 
274   public void clearHeaders() {
275     m_headers.clear();
276   }
277 
278   public void addHeader(String name, String value) {
279     m_headers.add(new NVPair(name, value));
280   }
281 
282   public void setBody(String body) {
283     m_body = body;
284   }
285 
286   public void setResponseDelay(long responseDelay) {
287     m_responseDelay = responseDelay;
288   }
289 }