• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / httpunit / 389

12 Aug 2025 11:17PM UTC coverage: 80.48% (-0.02%) from 80.503%
389

push

github

hazendaz
Merge branch 'master' into javax

3216 of 4105 branches covered (78.34%)

Branch coverage included in aggregate %.

238 of 258 new or added lines in 68 files covered. (92.25%)

2 existing lines in 2 files now uncovered.

8254 of 10147 relevant lines covered (81.34%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.78
/src/main/java/com/meterware/pseudoserver/PseudoServer.java
1
/*
2
 * MIT License
3
 *
4
 * Copyright 2011-2025 Russell Gold
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
8
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
9
 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
12
 * of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
15
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
17
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
 * DEALINGS IN THE SOFTWARE.
19
 */
20
package com.meterware.pseudoserver;
21

22
import com.meterware.httpunit.HttpUnitUtils;
23

24
import java.io.BufferedInputStream;
25
import java.io.File;
26
import java.io.FileInputStream;
27
import java.io.IOException;
28
import java.io.InputStream;
29
import java.io.InterruptedIOException;
30
import java.io.OutputStream;
31
import java.io.OutputStreamWriter;
32
import java.io.PrintStream;
33
import java.io.PrintWriter;
34
import java.net.HttpURLConnection;
35
import java.net.ServerSocket;
36
import java.net.Socket;
37
import java.nio.charset.Charset;
38
import java.util.ArrayList;
39
import java.util.Enumeration;
40
import java.util.Hashtable;
41
import java.util.Iterator;
42
import java.util.StringTokenizer;
43
import java.util.Vector;
44

45
/**
46
 * A basic simulated web-server for testing user agents without a web server.
47
 **/
48
public class PseudoServer {
49

50
    /**
51
     * allow factory use to be switched on and off by default the factory is not used any more since there were problems
52
     * with the test cases as of 2012-10-09
53
     */
54
    public static final boolean useFactory = false;
55

56
    static final int DEFAULT_SOCKET_TIMEOUT = 1000;
57

58
    private static final int INPUT_POLL_INTERVAL = 10;
59

60
    /** Time in msec to wait for an outstanding server socket to be released before creating a new one. **/
61
    private static int _socketReleaseWaitTime = 50;
1✔
62

63
    /** Number of outstanding server sockets that must be present before trying to wait for one to be released. **/
64
    private static int _waitThreshhold = 10;
1✔
65

66
    private static int _numServers = 0;
1✔
67

68
    private int _serverNum = 0;
1✔
69

70
    private int _connectionNum = 0;
1✔
71

72
    private ArrayList _classpathDirs = new ArrayList<>();
1✔
73

74
    private String _maxProtocolLevel = "1.1";
1✔
75

76
    private final int _socketTimeout;
77

78
    /**
79
     * Returns the amount of time the pseudo server will wait for a server socket to be released (in msec) before
80
     * allocating a new one. See also {@link #getWaitThreshhold getWaitThreshhold}.
81
     */
82
    public static int getSocketReleaseWaitTime() {
83
        return _socketReleaseWaitTime;
×
84
    }
85

86
    /**
87
     * Returns the amount of time the pseudo server will wait for a server socket to be released (in msec) before
88
     * allocating a new one. See also {@link #getWaitThreshhold getWaitThreshhold}.
89
     */
90
    public static void setSocketReleaseWaitTime(int socketReleaseWaitTime) {
91
        _socketReleaseWaitTime = socketReleaseWaitTime;
×
92
    }
×
93

94
    /**
95
     * Returns the number of server sockets that must have been allocated and not returned before waiting for one to be
96
     * returned.
97
     */
98
    public static int getWaitThreshhold() {
99
        return _waitThreshhold;
×
100
    }
101

102
    /**
103
     * Specifies the number of server sockets that must have been allocated and not returned before waiting for one to
104
     * be returned.
105
     */
106
    public static void setWaitThreshhold(int waitThreshhold) {
107
        _waitThreshhold = waitThreshhold;
×
108
    }
×
109

110
    public PseudoServer() {
111
        this(DEFAULT_SOCKET_TIMEOUT);
1✔
112
    }
1✔
113

114
    /**
115
     * create a PseudoServer with the given socketTimeout
116
     *
117
     * @param socketTimeout
118
     *            - the time out to use
119
     */
120
    public PseudoServer(int socketTimeout) {
1✔
121
        _socketTimeout = socketTimeout;
1✔
122
        _serverNum = ++_numServers;
1✔
123

124
        try {
125
            _serverSocket = new ServerSocket(0);
1✔
126
            _serverSocket.setSoTimeout(1000);
1✔
127
        } catch (IOException e) {
×
128
            System.out.println("Error while creating socket: " + e);
×
129
            throw new RuntimeException(e);
×
130
        }
1✔
131
        Thread t = new Thread("PseudoServer " + _serverNum) {
1✔
132
            @Override
133
            public void run() {
134
                while (_active) {
1✔
135
                    try {
136
                        handleNewConnection(_serverSocket.accept());
1✔
137
                        Thread.sleep(20);
1✔
138
                    } catch (InterruptedIOException e) {
1✔
139
                    } catch (IOException e) {
×
140
                        System.out.println("Error in pseudo server: " + e);
×
141
                        HttpUnitUtils.handleException(e);
×
142
                    } catch (InterruptedException e) {
×
NEW
143
                        Thread.interrupted();
×
144
                        System.out.println("Interrupted. Shutting down");
×
145
                        _active = false;
×
146
                    }
1✔
147
                }
148
                try {
149
                    _serverSocket.close();
1✔
150
                } catch (IOException e) {
×
151
                    System.out.println("Error while closing socket: " + e);
×
152
                }
1✔
153
                debug("Pseudoserver shutting down");
1✔
154
            }
1✔
155
        };
156
        debug("Starting pseudoserver");
1✔
157
        t.start();
1✔
158
    }
1✔
159

160
    public void shutDown() {
161
        debug("Requested shutdown of pseudoserver");
1✔
162
        _active = false;
1✔
163
    }
1✔
164

165
    private void debug(String message) {
166
        if (!_debug) {
1!
167
            return;
1✔
168
        }
169
        message = replaceDebugToken(message, "thread", "thread (" + Thread.currentThread().getName() + ")");
×
170
        message = replaceDebugToken(message, "server", "server " + _serverNum);
×
171
        System.out.println("** " + message);
×
172
    }
×
173

174
    private static String replaceDebugToken(String message, String token, String replacement) {
175
        return !message.contains(token) ? message : message.replaceFirst(token, replacement);
×
176
    }
177

178
    public void setMaxProtocolLevel(int majorLevel, int minorLevel) {
179
        _maxProtocolLevel = majorLevel + "." + minorLevel;
1✔
180
    }
1✔
181

182
    /**
183
     * Returns the port on which this server is listening.
184
     **/
185
    public int getConnectedPort() throws IOException {
186
        return _serverSocket.getLocalPort();
1✔
187
    }
188

189
    /**
190
     * Defines the contents of an expected resource.
191
     **/
192
    public void setResource(String name, String value) {
193
        setResource(name, value, "text/html");
1✔
194
    }
1✔
195

196
    /**
197
     * Defines the contents of an expected resource.
198
     **/
199
    public void setResource(String name, PseudoServlet servlet) {
200
        _resources.put(asResourceName(name), servlet);
1✔
201
    }
1✔
202

203
    /**
204
     * Defines the contents of an expected resource.
205
     **/
206
    public void setResource(String name, String value, String contentType) {
207
        _resources.put(asResourceName(name), new WebResource(value, contentType));
1✔
208
    }
1✔
209

210
    /**
211
     * Defines the contents of an expected resource.
212
     **/
213
    public void setResource(String name, byte[] value, String contentType) {
214
        _resources.put(asResourceName(name), new WebResource(value, contentType));
1✔
215
    }
1✔
216

217
    /**
218
     * Defines a resource which will result in an error message. return it for further use
219
     *
220
     * @param name
221
     * @param errorCode
222
     * @param errorMessage
223
     *
224
     * @return the resource
225
     */
226
    public WebResource setErrorResource(String name, int errorCode, String errorMessage) {
227
        WebResource resource = new WebResource(errorMessage, errorCode);
1✔
228
        _resources.put(asResourceName(name), resource);
1✔
229
        return resource;
1✔
230
    }
231

232
    /**
233
     * Enables the sending of the character set in the content-type header.
234
     **/
235
    public void setSendCharacterSet(String name, boolean enabled) {
236
        WebResource resource = (WebResource) _resources.get(asResourceName(name));
1✔
237
        if (resource == null) {
1!
238
            throw new IllegalArgumentException("No defined resource " + name);
×
239
        }
240
        resource.setSendCharacterSet(enabled);
1✔
241
    }
1✔
242

243
    /**
244
     * Specifies the character set encoding for a resource.
245
     **/
246
    public void setCharacterSet(String name, String characterSet) {
247
        WebResource resource = (WebResource) _resources.get(asResourceName(name));
1✔
248
        if (resource == null) {
1!
249
            resource = new WebResource("");
×
250
            _resources.put(asResourceName(name), resource);
×
251
        }
252
        resource.setCharacterSet(characterSet);
1✔
253
    }
1✔
254

255
    /**
256
     * Adds a header to a defined resource.
257
     **/
258
    public void addResourceHeader(String name, String header) {
259
        WebResource resource = (WebResource) _resources.get(asResourceName(name));
1✔
260
        if (resource == null) {
1!
261
            resource = new WebResource("");
×
262
            _resources.put(asResourceName(name), resource);
×
263
        }
264
        resource.addHeader(header);
1✔
265
    }
1✔
266

267
    public void mapToClasspath(String directory) {
268
        _classpathDirs.add(directory);
1✔
269
    }
1✔
270

271
    public void setDebug(boolean debug) {
272
        _debug = debug;
×
273
    }
×
274

275
    // ------------------------------------- private members ---------------------------------------
276

277
    private Hashtable _resources = new Hashtable<>();
1✔
278

279
    private boolean _active = true;
1✔
280

281
    private boolean _debug = false;
1✔
282

283
    private String asResourceName(String rawName) {
284
        if (rawName.startsWith("http:") || rawName.startsWith("/")) {
1✔
285
            return escape(rawName);
1✔
286
        }
287
        return escape("/" + rawName);
1✔
288
    }
289

290
    private static String escape(String urlString) {
291
        if (urlString.indexOf(' ') < 0) {
1✔
292
            return urlString;
1✔
293
        }
294
        StringBuilder sb = new StringBuilder();
1✔
295

296
        int start = 0;
1✔
297
        do {
298
            int index = urlString.indexOf(' ', start);
1✔
299
            if (index < 0) {
1✔
300
                sb.append(urlString.substring(start));
1✔
301
                break;
1✔
302
            }
303
            sb.append(urlString.substring(start, index)).append("%20");
1✔
304
            start = index + 1;
1✔
305
        } while (true);
1✔
306
        return sb.toString();
1✔
307
    }
308

309
    private void handleNewConnection(final Socket socket) {
310
        Thread t = new Thread("PseudoServer " + _serverNum + " connection " + (++_connectionNum)) {
1✔
311
            @Override
312
            public void run() {
313
                try {
314
                    serveRequests(socket);
1✔
315
                } catch (IOException e) {
×
316
                    e.printStackTrace(); // To change body of catch statement use Options | File Templates.
×
317
                }
1✔
318
            }
1✔
319
        };
320
        t.start();
1✔
321
    }
1✔
322

323
    private void serveRequests(Socket socket) throws IOException {
324
        socket.setSoTimeout(_socketTimeout);
1✔
325
        socket.setTcpNoDelay(true);
1✔
326

327
        debug("Created server thread " + socket.getInetAddress() + ':' + socket.getPort());
1✔
328
        final BufferedInputStream inputStream = new BufferedInputStream(socket.getInputStream());
1✔
329
        final HttpResponseStream outputStream = new HttpResponseStream(socket.getOutputStream());
1✔
330

331
        try {
332
            while (_active) {
1✔
333
                HttpRequest request = new HttpRequest(inputStream);
1✔
334
                boolean keepAlive = respondToRequest(request, outputStream);
1✔
335
                if (!keepAlive) {
1✔
336
                    break;
1✔
337
                }
338
                while (_active && 0 == inputStream.available()) {
1✔
339
                    try {
340
                        Thread.sleep(INPUT_POLL_INTERVAL);
1✔
341
                    } catch (InterruptedException e) {
×
NEW
342
                        Thread.interrupted();
×
343
                    }
1✔
344
                }
345
            }
1✔
346
        } catch (IOException e) {
1✔
347
            outputStream.restart();
1✔
348
            outputStream.setProtocol("HTTP/1.0");
1✔
349
            outputStream.setResponse(HttpURLConnection.HTTP_BAD_REQUEST, e.toString());
1✔
350
        }
1✔
351
        debug("Closing server thread");
1✔
352
        outputStream.close();
1✔
353
        socket.close();
1✔
354
        debug("Server thread closed");
1✔
355
    }
1✔
356

357
    /**
358
     * respond to the given request
359
     *
360
     * @param request
361
     *            - the request
362
     * @param response
363
     *            - the response stream
364
     *
365
     * @return
366
     */
367
    private boolean respondToRequest(HttpRequest request, HttpResponseStream response) {
368
        debug("Server thread handling request: " + request);
1✔
369
        boolean keepAlive = isKeepAlive(request);
1✔
370
        WebResource resource = null;
1✔
371
        try {
372
            response.restart();
1✔
373
            response.setProtocol(getResponseProtocol(request));
1✔
374
            resource = getResource(request);
1✔
375
            if (resource == null) {
1✔
376
                // what resource could not be find?
377
                String uri = request.getURI();
1✔
378
                // 404 - Not Found error code
379
                int errorCode = HttpURLConnection.HTTP_NOT_FOUND;
1✔
380
                // typical 404 error Message
381
                String errorMessage = "unable to find " + uri;
1✔
382
                // make sure there is a resource and
383
                // next time we'll take it from the resource Cache
384
                resource = setErrorResource(uri, errorCode, errorMessage);
1✔
385
                // set the errorCode for this response
386
                response.setResponse(errorCode, errorMessage);
1✔
387
            } else if (resource.getResponseCode() != HttpURLConnection.HTTP_OK) {
1✔
388
                response.setResponse(resource.getResponseCode(), "");
1✔
389
            }
390
            if (resource.closesConnection()) {
1✔
391
                keepAlive = false;
1✔
392
            }
393
            String[] headers = resource.getHeaders();
1✔
394
            for (String header : headers) {
1✔
395
                debug("Server thread sending header: " + header);
1✔
396
                response.addHeader(header);
1✔
397
            }
398
        } catch (UnknownMethodException e) {
1✔
399
            response.setResponse(HttpURLConnection.HTTP_BAD_METHOD, "unsupported method: " + e.getMethod());
1✔
400
        } catch (Throwable t) {
×
401
            t.printStackTrace();
×
402
            response.setResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, t.toString());
×
403
        }
1✔
404
        try {
405
            response.write(resource);
1✔
406
        } catch (IOException e) {
×
407
            System.out.println("*** Failed to send reply: " + e);
×
408
        }
1✔
409
        return keepAlive;
1✔
410
    }
411

412
    private boolean isKeepAlive(HttpRequest request) {
413
        return request.wantsKeepAlive() && _maxProtocolLevel.equals("1.1");
1!
414
    }
415

416
    private String getResponseProtocol(HttpRequest request) {
417
        return _maxProtocolLevel.equalsIgnoreCase("1.1") ? request.getProtocol() : "HTTP/1.0";
1✔
418
    }
419

420
    /**
421
     * get the resource for the given request by first trying to look it up in the cache then depending on the type of
422
     * request PseudoServlet and the method / command e.g. GET/HEAD finally the extension of the uri ".zip" ".class" and
423
     * ".jar" are handled
424
     *
425
     * @param request
426
     *
427
     * @return the WebResource or null if non of the recipes above will lead to a valid resource
428
     *
429
     * @throws IOException
430
     */
431
    private WebResource getResource(HttpRequest request) throws IOException {
432
        Object resource = _resources.get(request.getURI());
1✔
433
        if (resource == null) {
1✔
434
            resource = _resources.get(withoutParameters(request.getURI()));
1✔
435
        }
436

437
        // check the method of the request
438
        String command = request.getCommand();
1✔
439
        if ((command.equals("GET") || command.equals("HEAD")) && resource instanceof WebResource) {
1✔
440
            return (WebResource) resource;
1✔
441
        }
442
        if (resource instanceof PseudoServlet) {
1✔
443
            return getResource((PseudoServlet) resource, request);
1✔
444
        }
445
        if (request.getURI().endsWith(".class")) {
1✔
446
            for (Iterator iterator = _classpathDirs.iterator(); iterator.hasNext();) {
1!
447
                String directory = (String) iterator.next();
1✔
448
                if (request.getURI().startsWith(directory)) {
1!
449
                    String resourceName = request.getURI().substring(directory.length() + 1);
1✔
450
                    return new WebResource(getClass().getClassLoader().getResourceAsStream(resourceName),
1✔
451
                            "application/class", 200);
452
                }
453
            }
×
454
        } else if (request.getURI().endsWith(".zip") || request.getURI().endsWith(".jar")) {
1!
455
            for (Iterator iterator = _classpathDirs.iterator(); iterator.hasNext();) {
×
456
                String directory = (String) iterator.next();
×
457
                if (request.getURI().startsWith(directory)) {
×
458
                    String resourceName = request.getURI().substring(directory.length() + 1);
×
459
                    String classPath = System.getProperty("java.class.path");
×
460
                    StringTokenizer st = new StringTokenizer(classPath, ":;,");
×
461
                    while (st.hasMoreTokens()) {
×
462
                        String file = st.nextToken();
×
463
                        if (file.endsWith(resourceName)) {
×
464
                            File f = new File(file);
×
465
                            return new WebResource(new FileInputStream(f), "application/zip", 200);
×
466
                        }
467
                    }
×
468
                }
469
            }
×
470
        }
471
        return null;
1✔
472
    }
473

474
    private String withoutParameters(String uri) {
475
        return uri.indexOf('?') < 0 ? uri : uri.substring(0, uri.indexOf('?'));
1✔
476
    }
477

478
    private WebResource getResource(PseudoServlet servlet, HttpRequest request) throws IOException {
479
        servlet.init(request);
1✔
480
        return servlet.getResponse(request.getCommand());
1✔
481
    }
482

483
    private ServerSocket _serverSocket;
484

485
}
486

487
class HttpResponseStream {
488

489
    private static final String CRLF = "\r\n";
490

491
    void restart() {
492
        _headersWritten = false;
1✔
493
        _headers.clear();
1✔
494
        _responseCode = HttpURLConnection.HTTP_OK;
1✔
495
        _responseText = "OK";
1✔
496
    }
1✔
497

498
    void close() throws IOException {
499
        flushHeaders();
1✔
500
        _pw.close();
1✔
501
    }
1✔
502

503
    HttpResponseStream(OutputStream stream) {
1✔
504
        _stream = stream;
1✔
505
        setCharacterSet("us-ascii");
1✔
506
    }
1✔
507

508
    void setProtocol(String protocol) {
509
        _protocol = protocol;
1✔
510
    }
1✔
511

512
    /**
513
     * set the response to the given response Code
514
     *
515
     * @param responseCode
516
     * @param responseText
517
     */
518
    void setResponse(int responseCode, String responseText) {
519
        _responseCode = responseCode;
1✔
520
        _responseText = responseText;
1✔
521
    }
1✔
522

523
    void addHeader(String header) {
524
        _headers.addElement(header);
1✔
525
    }
1✔
526

527
    void write(String contents, String charset) throws IOException {
528
        flushHeaders();
×
529
        setCharacterSet(charset);
×
530
        sendText(contents);
×
531
    }
×
532

533
    void write(WebResource resource) throws IOException {
534
        flushHeaders();
1✔
535
        if (resource != null) {
1✔
536
            resource.writeTo(_stream);
1✔
537
        }
538
        _stream.flush();
1✔
539
    }
1✔
540

541
    private void setCharacterSet(String characterSet) {
542
        if (_pw != null) {
1!
543
            _pw.flush();
×
544
        }
545
        _pw = new PrintWriter(new OutputStreamWriter(_stream, Charset.forName(characterSet)));
1✔
546
    }
1✔
547

548
    private void flushHeaders() {
549
        if (!_headersWritten) {
1✔
550
            sendResponse(_responseCode, _responseText);
1✔
551
            for (Enumeration e = _headers.elements(); e.hasMoreElements();) {
1✔
552
                sendLine((String) e.nextElement());
1✔
553
            }
554
            sendText(CRLF);
1✔
555
            _headersWritten = true;
1✔
556
            _pw.flush();
1✔
557
        }
558
    }
1✔
559

560
    private void sendResponse(int responseCode, String responseText) {
561
        sendLine(_protocol + ' ' + responseCode + ' ' + responseText);
1✔
562
    }
1✔
563

564
    private void sendLine(String text) {
565
        sendText(text);
1✔
566
        sendText(CRLF);
1✔
567
    }
1✔
568

569
    private void sendText(String text) {
570
        _pw.write(text);
1✔
571
    }
1✔
572

573
    private OutputStream _stream;
574
    private PrintWriter _pw;
575

576
    private Vector _headers = new Vector<>();
1✔
577
    private String _protocol = "HTTP/1.0";
1✔
578
    private int _responseCode = HttpURLConnection.HTTP_OK;
1✔
579
    private String _responseText = "OK";
1✔
580

581
    private boolean _headersWritten;
582

583
}
584

585
class RecordingOutputStream extends OutputStream {
586

587
    private OutputStream _nestedStream;
588
    private PrintStream _log;
589

590
    public RecordingOutputStream(OutputStream nestedStream, PrintStream log) {
×
591
        _nestedStream = nestedStream;
×
592
        _log = log;
×
593
    }
×
594

595
    @Override
596
    public void write(int b) throws IOException {
597
        _nestedStream.write(b);
×
598
        _log.println("sending " + Integer.toHexString(b));
×
599
    }
×
600

601
    @Override
602
    public void write(byte b[], int offset, int len) throws IOException {
603
        _nestedStream.write(b, offset, len);
×
604
        _log.print("sending");
×
605
        for (int i = offset; i < offset + len; i++) {
×
606
            _log.print(' ' + Integer.toHexString(b[i]));
×
607
        }
608
        _log.println();
×
609
    }
×
610
}
611

612
class RecordingInputStream extends InputStream {
613

614
    private InputStream _nestedStream;
615
    private PrintStream _log;
616

617
    public RecordingInputStream(InputStream nestedStream, PrintStream log) {
×
618
        _nestedStream = nestedStream;
×
619
        _log = log;
×
620
    }
×
621

622
    @Override
623
    public int read() throws IOException {
624
        int value = _nestedStream.read();
×
625
        if (value != -1) {
×
626
            _log.print(' ' + Integer.toHexString(value));
×
627
        }
628
        return value;
×
629
    }
630
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc