001: /*
002: * ========================================================================
003: *
004: * Copyright 2003 The Apache Software Foundation.
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * ========================================================================
019: */
020: package org.apache.cactus.integration.ant.container;
021:
022: import java.io.BufferedReader;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.InputStreamReader;
026: import java.io.OutputStream;
027: import java.io.StringReader;
028: import java.net.ConnectException;
029: import java.net.ServerSocket;
030: import java.net.Socket;
031: import java.util.Random;
032: import java.util.StringTokenizer;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036:
037: import junit.framework.Assert;
038:
039: /**
040: * A very simple HTTP server that binds to a port and responds to all requests
041: * with a predefined response.
042: *
043: * @version $Id: MockHttpServer.java 238816 2004-02-29 16:36:46Z vmassol $
044: */
045: public final class MockHttpServer implements Runnable {
046:
047: // Instance Variables ------------------------------------------------------
048:
049: /**
050: * The port to bind to.
051: */
052: private int port;
053:
054: /**
055: * The content of the request to send back on any request.
056: */
057: private String response;
058:
059: /**
060: * Flag indicating whether the server should be stopped.
061: */
062: private volatile boolean stopFlag = false;
063:
064: /**
065: * The actual method requested.
066: */
067: private String actualMethod;
068:
069: /**
070: * The HTTP method expected.
071: */
072: private String expectedMethod;
073:
074: /**
075: * The actual request URI.
076: */
077: private String actualUri;
078:
079: /**
080: * The request URI expected.
081: */
082: private String expectedUri;
083:
084: /**
085: * The expected number of requests.
086: */
087: private int expectedRequestCount = -1;
088:
089: /**
090: * The number of requests (excluding the internal SHUTDOWN request).
091: */
092: private int actualRequestCount = 0;
093:
094: /**
095: * The log to write messages to.
096: */
097: private Log log = LogFactory.getLog(MockHttpServer.class);
098:
099: // Constructors ------------------------------------------------------------
100:
101: /**
102: * Constructor.
103: *
104: * @param thePort The port to bind to
105: */
106: public MockHttpServer(int thePort) {
107: if (thePort <= 0) {
108: throw new IllegalArgumentException("Invalid port number");
109: }
110: this .port = thePort;
111: }
112:
113: // Runnable Implementation -------------------------------------------------
114:
115: /**
116: * The main server thread. The server will wait for connections until it
117: * receives a special request containing the string 'SHUTDOWN'.
118: */
119: public void run() {
120: if (this .response == null) {
121: throw new IllegalStateException("Response content not set");
122: }
123:
124: try {
125: ServerSocket serverSocket = new ServerSocket(port);
126: while (!this .stopFlag) {
127: Socket socket = serverSocket.accept();
128: try {
129: if (!this .stopFlag) {
130: processRequest(socket);
131: }
132: } catch (IOException ioe) {
133: this .log.error("Couldn't process request", ioe);
134: } finally {
135: socket.close();
136: }
137: }
138: serverSocket.close();
139: } catch (IOException ioe) {
140: this .log.error("Problem with server socket", ioe);
141: }
142: }
143:
144: // Public Methods ----------------------------------------------------------
145:
146: /**
147: * Advise the server to expect a specific HTTP method in requests.
148: *
149: * @param theMethod The HTTP method to expect
150: */
151: public void expectMethod(String theMethod) {
152: this .expectedMethod = theMethod;
153: }
154:
155: /**
156: * Advise the server to expect a specific number of requests.
157: *
158: * @param theRequestCount The number of requests to expect
159: */
160: public void expectRequestCount(int theRequestCount) {
161: this .expectedRequestCount = theRequestCount;
162: }
163:
164: /**
165: * Advise the server to expect a specific request URI in requests.
166: *
167: * @param theUri The request URI to expect
168: */
169: public void expectUri(String theUri) {
170: this .expectedUri = theUri;
171: }
172:
173: /**
174: * Returns the port number the server is listening on.
175: *
176: * @return The port
177: */
178: public int getPort() {
179: return this .port;
180: }
181:
182: /**
183: * Returns whether the server is stopped (or about to stop, to be precise).
184: *
185: * @return Whether the server is stopped
186: */
187: public boolean isStopped() {
188: return this .stopFlag;
189: }
190:
191: /**
192: * Sets the content of the request to send back on any request.
193: *
194: * @param theResponse The content of the HTTP response
195: */
196: public void setResponse(String theResponse) {
197: this .response = theResponse;
198: }
199:
200: /**
201: * Stops the server.
202: */
203: public void stop() {
204: this .stopFlag = true;
205: try {
206: Socket sock = new Socket("localhost", this .port);
207: sock.getOutputStream().write("SHUTDOWN\n".getBytes());
208: } catch (IOException ioe) {
209: this .log.error("Error while trying to stop", ioe);
210: }
211: }
212:
213: /**
214: * Verifies whether the requests sent to the server matched those expected.
215: */
216: public void verify() {
217: if (this .expectedRequestCount >= 0) {
218: Assert
219: .assertTrue(
220: "Expected " + this .expectedRequestCount
221: + " requests, but got "
222: + this .actualRequestCount,
223: this .expectedRequestCount == this .actualRequestCount);
224: }
225: if (this .expectedMethod != null) {
226: Assert.assertEquals(this .expectedMethod, this .actualMethod);
227: }
228: if (this .expectedUri != null) {
229: Assert.assertEquals(this .expectedUri, this .actualUri);
230: }
231: }
232:
233: // Public Static Methods ---------------------------------------------------
234:
235: /**
236: * Returns a free port number on the specified host within the given range.
237: *
238: * @param theHost The name or IP addres of host on which to find a free port
239: * @param theLowest The port number from which to start searching
240: * @param theHighest The port number at which to stop searching
241: * @return A free port in the specified range, or -1 of none was found
242: * @throws IOException If an I/O error occurs
243: */
244: public static int findUnusedLocalPort(String theHost,
245: int theLowest, int theHighest) throws IOException {
246: final Random random = new Random(System.currentTimeMillis());
247: for (int i = 0; i < 10; i++) {
248: int port = (int) (random.nextFloat() * (theHighest - theLowest))
249: + theLowest;
250: Socket s = null;
251: try {
252: s = new Socket(theHost, port);
253: } catch (ConnectException e) {
254: return port;
255: } finally {
256: if (s != null) {
257: s.close();
258: }
259: }
260: }
261: return -1;
262: }
263:
264: // Private Methods ---------------------------------------------------------
265:
266: /**
267: * Processes an incoming request.
268: *
269: * @param theSocket The socket to which the connection was established
270: * @throws IOException If an I/O error occurs
271: */
272: private void processRequest(Socket theSocket) throws IOException {
273: BufferedReader in = null;
274: OutputStream out = null;
275: try {
276: readRequest(theSocket.getInputStream());
277: writeResponse(theSocket.getOutputStream());
278: } finally {
279: if (in != null) {
280: in.close();
281: }
282: if (out != null) {
283: out.close();
284: }
285: }
286: }
287:
288: /**
289: * Reads the request and stores the HTTP method and request URI.
290: *
291: * @param theIn The socket input stream
292: * @throws IOException If an I/O error occurs
293: */
294: private void readRequest(InputStream theIn) throws IOException {
295: BufferedReader reader = new BufferedReader(
296: new InputStreamReader(theIn));
297:
298: String statusLine = reader.readLine();
299: StringTokenizer tokenizer = new StringTokenizer(statusLine);
300: this .actualRequestCount++;
301: this .actualMethod = tokenizer.nextToken();
302: this .actualUri = tokenizer.nextToken();
303: }
304:
305: /**
306: * Writes the user-defined response to the socket output stream.
307: *
308: * @param theOut The socket output stream
309: * @throws IOException If an I/O error occurs
310: */
311: private void writeResponse(OutputStream theOut) throws IOException {
312: // Construct the response message.
313: if (this .response != null) {
314: BufferedReader reader = new BufferedReader(
315: new StringReader(this .response));
316: String line = null;
317: while ((line = reader.readLine()) != null) {
318: theOut.write(line.getBytes());
319: theOut.write("\r\n".getBytes());
320: }
321: }
322: }
323:
324: }
|