Response.java

1
package com.renomad.minum.web;
2
3
import com.renomad.minum.utils.IFileUtils;
4
5
import java.io.IOException;
6
import java.io.RandomAccessFile;
7
import java.net.URI;
8
import java.nio.ByteBuffer;
9
import java.nio.channels.FileChannel;
10
import java.nio.charset.StandardCharsets;
11
import java.nio.file.Path;
12
import java.util.*;
13
14
import static com.renomad.minum.web.StatusLine.StatusCode.CODE_200_OK;
15
import static com.renomad.minum.web.StatusLine.StatusCode.CODE_206_PARTIAL_CONTENT;
16
17
/**
18
 * Represents an HTTP response. This is what will get sent back to the
19
 * client (that is, to the browser).  There are a variety of overloads
20
 * of this record for different situations.  The overarching paradigm is
21
 * to provide you high flexibility.
22
 * <p>
23
 * A response message is sent by a server to a client as a reply to its former request message.
24
 * </p>
25
 * <h3>
26
 * Response syntax
27
 * </h3>
28
 * <p>
29
 * A server sends response messages to the client, which consist of:
30
 * </p>
31
 * <ul>
32
 * <li>
33
 * a status line, consisting of the protocol version, a space, the
34
 * response status code, another space, a possibly empty reason
35
 * phrase, a carriage return and a line feed, e.g.:
36
 * <pre>
37
 * HTTP/1.1 200 OK
38
 * </pre>
39
 * </li>
40
 *
41
 * <li>
42
 * zero or more response header fields, each consisting of the case-insensitive
43
 * field name, a colon, optional leading whitespace, the field value, an
44
 * optional trailing whitespace and ending with a carriage return and a line feed, e.g.:
45
 * <pre>
46
 * Content-Type: text/html
47
 * </pre>
48
 * </li>
49
 *
50
 * <li>
51
 * an empty line, consisting of a carriage return and a line feed;
52
 * </li>
53
 *
54
 * <li>
55
 * an optional message body.
56
 * </li>
57
 *</ul>
58
59
 */
60
public final class Response implements IResponse {
61
62
    private final StatusLine.StatusCode statusCode;
63
    private final Headers extraHeaders;
64
    private final byte[] body;
65
    private final ThrowingConsumer<ISocketWrapper> outputGenerator;
66
    private final long bodyLength;
67
68
    /**
69
     * If true, the body is text, and therefore would probably benefit
70
     * from being compressed before sending (presuming sufficiently large
71
     * to make it worth the time for compression)
72
     */
73
    private final boolean isBodyText;
74
75
    /**
76
     * This is the constructor that provides access to all fields.  It is not intended
77
     * to be used from outside this class.
78
     * @param extraHeaders extra headers we want to return with the response.
79
     * @param outputGenerator a {@link ThrowingConsumer} that will use a {@link ISocketWrapper} parameter
80
     *                        to send bytes on the wire back to the client.  See the static factory methods
81
     *                        such as {@link #buildResponse(StatusLine.StatusCode, Map, byte[])} for more details on this.
82
     * @param bodyLength this is used to set the content-length header for the response.  If this is
83
     *                   not provided, we set the header to "transfer-encoding: chunked", or in other words, streaming.
84
     * @param isBodyText If true, the body is text, and therefore would probably benefit
85
     *                   from being compressed before sending (presuming sufficiently large
86
     *                   to make it worth the time for compression)
87
     */
88
    Response(StatusLine.StatusCode statusCode, Headers extraHeaders, byte[] body,
89
             ThrowingConsumer<ISocketWrapper> outputGenerator, long bodyLength, boolean isBodyText) {
90 1 1. <init> : negated conditional → KILLED
        if (statusCode == null) throw new IllegalArgumentException("Status code must not be null");
91 1 1. <init> : negated conditional → KILLED
        if (extraHeaders == null) throw new IllegalArgumentException("Extra headers must not be null (may use Headers.EMPTY)");
92
        this.statusCode = statusCode;
93
        this.extraHeaders = extraHeaders;
94
        this.body = body;
95
        this.outputGenerator = outputGenerator;
96
        this.bodyLength = bodyLength;
97
        this.isBodyText = isBodyText;
98
    }
99
100
    /**
101
     * This factory method is intended for situations where the user wishes to stream data
102
     * but lacks the content length.  This is only for unusual situations where the developer
103
     * needs the extra control.  In most cases, other methods are more suitable.
104
     * @param extraHeaders any extra headers for the response, such as the content-type
105
     * @param outputGenerator a function that will be given a {@link ISocketWrapper}, providing the
106
     *                        ability to send bytes on the socket.
107
     */
108
    public static IResponse buildStreamingResponse(StatusLine.StatusCode statusCode, Headers extraHeaders, ThrowingConsumer<ISocketWrapper> outputGenerator) {
109 1 1. buildStreamingResponse : replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED
        return new Response(statusCode, extraHeaders, null, outputGenerator, 0, false);
110
    }
111
112
    /**
113
     * This factory method is intended for situations where the user wishes to stream data
114
     * but lacks the content length.  This is only for unusual situations where the developer
115
     * needs the extra control.  In most cases, other methods are more suitable.
116
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
117
     *                     key is the header key and the value is the header value. No colon necessary.
118
     * @param outputGenerator a function that will be given a {@link ISocketWrapper}, providing the
119
     *                        ability to send bytes on the socket.
120
     */
121
    public static IResponse buildStreamingResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders, ThrowingConsumer<ISocketWrapper> outputGenerator) {
122 1 1. buildStreamingResponse : replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED
        return buildStreamingResponse(statusCode, convertMapToHeaders(extraHeaders), outputGenerator);
123
    }
124
125
    /**
126
     * Similar to {@link Response#buildStreamingResponse(StatusLine.StatusCode, Headers, ThrowingConsumer)} but here we know
127
     * the body length, so that will be sent to the client as content-length.
128
     * @param extraHeaders any extra headers for the response, such as the content-type
129
     * @param outputGenerator a function that will be given a {@link ISocketWrapper}, providing the
130
     *                        ability to send bytes on the socket.
131
     * @param bodyLength the length, in bytes, of the data to be sent to the client
132
     */
133
    public static IResponse buildStreamingResponse(StatusLine.StatusCode statusCode, Headers extraHeaders, ThrowingConsumer<ISocketWrapper> outputGenerator, long bodyLength) {
134 1 1. buildStreamingResponse : replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED
        return new Response(statusCode, extraHeaders, null, outputGenerator, bodyLength, false);
135
    }
136
137
    /**
138
     * Similar to {@link Response#buildStreamingResponse(StatusLine.StatusCode, Headers, ThrowingConsumer)} but here we know
139
     * the body length, so that will be sent to the client as content-length.
140
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
141
     *                     key is the header key and the value is the header value. No colon necessary.
142
     * @param outputGenerator a function that will be given a {@link ISocketWrapper}, providing the
143
     *                        ability to send bytes on the socket.
144
     * @param bodyLength the length, in bytes, of the data to be sent to the client
145
     */
146
    public static IResponse buildStreamingResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders, ThrowingConsumer<ISocketWrapper> outputGenerator, long bodyLength) {
147 1 1. buildStreamingResponse : replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED
        return buildStreamingResponse(statusCode, convertMapToHeaders(extraHeaders), outputGenerator, bodyLength);
148
    }
149
150
    /**
151
     * A constructor for situations where the developer wishes to send a small (less than a megabyte) byte array
152
     * to the client.  If there is need to send something of larger size, choose one these
153
     * alternate constructors:
154
     * FileChannel - for sending a large file: {@link Response#buildLargeFileResponse(Headers, String, String, Headers, IFileUtils)}
155
     * Streaming - for more custom streaming control with a known body size: {@link Response#buildStreamingResponse(StatusLine.StatusCode, Map, ThrowingConsumer, long)}
156
     * Streaming - for more custom streaming control with body size unknown: {@link Response#buildStreamingResponse(StatusLine.StatusCode, Map, ThrowingConsumer)}
157
     *
158
     * @param extraHeaders any extra headers for the response, such as the content-type.
159
     */
160
    public static IResponse buildResponse(StatusLine.StatusCode statusCode, Headers extraHeaders, byte[] body) {
161 2 1. lambda$buildResponse$0 : removed call to com/renomad/minum/web/Response::sendByteArrayResponse → KILLED
2. buildResponse : replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED
        return new Response(statusCode, extraHeaders, body, socketWrapper -> sendByteArrayResponse(socketWrapper, body), body.length, false);
162
    }
163
164
    /**
165
     * A constructor for situations where the developer wishes to send a small (less than a megabyte) byte array
166
     * to the client.  If there is need to send something of larger size, choose one these
167
     * alternate constructors:
168
     * FileChannel - for sending a large file: {@link Response#buildLargeFileResponse(Headers, String, String, Headers, IFileUtils)}
169
     * Streaming - for more custom streaming control with a known body size: {@link Response#buildStreamingResponse(StatusLine.StatusCode, Headers, ThrowingConsumer, long)}
170
     * Streaming - for more custom streaming control with body size unknown: {@link Response#buildStreamingResponse(StatusLine.StatusCode, Headers, ThrowingConsumer)}
171
     *
172
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
173
     *                     key is the header key and the value is the header value. No colon necessary.
174
     */
175
    public static IResponse buildResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders, byte[] body) {
176 1 1. buildResponse : replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED
        return buildResponse(statusCode, convertMapToHeaders(extraHeaders), body);
177
    }
178
179
    private static Headers convertMapToHeaders(Map<String, String> extraHeaders) {
180 2 1. convertMapToHeaders : replaced return value with null for com/renomad/minum/web/Response::convertMapToHeaders → KILLED
2. convertMapToHeaders : negated conditional → KILLED
        if (extraHeaders.isEmpty()) return Headers.EMPTY;
181 2 1. convertMapToHeaders : replaced return value with null for com/renomad/minum/web/Response::convertMapToHeaders → KILLED
2. lambda$convertMapToHeaders$1 : replaced return value with "" for com/renomad/minum/web/Response::lambda$convertMapToHeaders$1 → KILLED
        return new Headers(extraHeaders.entrySet().stream().map(x -> x.getKey() + ": " + x.getValue()).toList());
182
    }
183
184
    /**
185
     * Build an ordinary response, with a known body
186
     * @param extraHeaders extra HTTP headers, like <pre>content-type: text/html</pre>
187
     */
188
    public static IResponse buildResponse(StatusLine.StatusCode statusCode, Headers extraHeaders, String body) {
189
        byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
190 2 1. buildResponse : replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED
2. lambda$buildResponse$2 : removed call to com/renomad/minum/web/Response::sendByteArrayResponse → KILLED
        return new Response(statusCode, extraHeaders, bytes, socketWrapper -> sendByteArrayResponse(socketWrapper, bytes), bytes.length, true);
191
    }
192
193
    /**
194
     * Build an ordinary response, with a known body
195
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
196
     *                     key is the header key and the value is the header value. No colon necessary.
197
     */
198
    public static IResponse buildResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders, String body) {
199 1 1. buildResponse : replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED
        return buildResponse(statusCode, convertMapToHeaders(extraHeaders), body);
200
    }
201
202
203
    /**
204
     * This will send a large file to the client as a stream.
205
     * <em>Note that this does not check that the requested file is within a directory. If you expect the
206
     * path value to be untrusted - that is, set by a user - use {@link #buildLargeFileResponse(Headers, String, String, Headers, IFileUtils)}</em>
207
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
208
     *                     key is the header key and the value is the header value. No colon necessary.
209
     *                     For example "Content-Type: video/mp4" or "Content-Type: application/zip"
210
     * @param filePath the string value of the path to this file. <em>Caution! if this is set by the user.  See notes above</em>
211
     * @param requestHeaders these are the headers from the request, which is needed here to set proper
212
     *                       response headers in some cases.
213
     */
214
    public static IResponse buildLargeFileResponse(Map<String,String> extraHeaders, String filePath, Headers requestHeaders, IFileUtils fileUtils) {
215 1 1. buildLargeFileResponse : replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED
        return buildLargeFileResponse(convertMapToHeaders(extraHeaders), filePath, requestHeaders, fileUtils);
216
    }
217
218
    /**
219
     * This will send a large file to the client as a stream.
220
     * <em>Note that this does not check that the requested file is within a directory. If you expect the
221
     * path value to be untrusted - that is, set by a user - use {@link #buildLargeFileResponse(Headers, String, String, Headers, IFileUtils)}</em>
222
     * @param extraHeaders extra headers you would like in the response, as key-value pairs in a map - for
223
     *                     example "Content-Type: video/mp4" or "Content-Type: application/zip"
224
     * @param filePath the string value of the path to this file. <em>Caution! if this is set by the user.  See notes above</em>
225
     * @param requestHeaders these are the headers from the request, which is needed here to set proper
226
     *                       response headers in some cases.
227
     */
228
    public static IResponse buildLargeFileResponse(Headers extraHeaders, String filePath, Headers requestHeaders, IFileUtils fileUtils) {
229
        Headers adjustedHeaders = extraHeaders;
230
        long fileSize = 0;
231
        try {
232
            fileSize = fileUtils.size(Path.of(filePath));
233
        } catch (IOException e) {
234
            throw new WebServerException("Error in Response.buildLargeFileResponse", e);
235
        }
236
        var range = new Range(requestHeaders, fileSize);
237
        StatusLine.StatusCode responseCode = CODE_200_OK;
238
        long length = fileSize;
239 1 1. buildLargeFileResponse : negated conditional → KILLED
        if (range.hasRangeHeader()) {
240
            long offset = range.getOffset();
241
            length = range.getLength();
242 2 1. buildLargeFileResponse : Replaced long addition with subtraction → SURVIVED
2. buildLargeFileResponse : Replaced long subtraction with addition → KILLED
            var lastIndex = (offset + length) - 1;
243
            List<String> headerStrings = new ArrayList<>(extraHeaders.getHeaderStrings());
244
            headerStrings.add("Content-Range: " + String.format("bytes %d-%d/%d", offset, lastIndex, fileSize));
245
            adjustedHeaders = new Headers(headerStrings);
246
            responseCode = CODE_206_PARTIAL_CONTENT;
247
        }
248
249
        ThrowingConsumer<ISocketWrapper> outputGenerator = socketWrapper -> {
250
            try (RandomAccessFile reader = new RandomAccessFile(filePath, "r")) {
251 1 1. lambda$buildLargeFileResponse$3 : removed call to java/io/RandomAccessFile::seek → KILLED
                reader.seek(range.getOffset());
252
                var fileChannel = reader.getChannel();
253 1 1. lambda$buildLargeFileResponse$3 : removed call to com/renomad/minum/web/Response::sendFileChannelResponse → KILLED
                sendFileChannelResponse(socketWrapper, fileChannel, range.getLength());
254
            }
255
        };
256
257 1 1. buildLargeFileResponse : replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED
        return new Response(responseCode, adjustedHeaders, null, outputGenerator, length, false);
258
    }
259
260
    /**
261
     * This will send a large file to the client as a stream.
262
     * This is a safe version of the method, for when the file path is set by a user, meaning it is untrusted.
263
     * By setting a parentDirectory, the system will ensure that the requested path is within that directory, thus
264
     * disallowing path strings that could escape it, like prefixing with a slash.
265
     * @param extraHeaders extra headers you would like in the response, as key-value pairs in a map - for
266
     *                     example "Content-Type: video/mp4" or "Content-Type: application/zip"
267
     * @param filePath the string value of the path to this file. <em>Caution! if this is set by the user.  See notes above</em>
268
     * @param requestHeaders these are the headers from the request, which is needed here to set proper
269
     *                       response headers in some cases.
270
     * @param parentDirectory this value is presumed to be set by the developer, and thus isn't checked for safety. This
271
     *                        allows the developer to use directories anywhere in the system.
272
     */
273
    public static IResponse buildLargeFileResponse(Headers extraHeaders, String filePath, String parentDirectory, Headers requestHeaders, IFileUtils fileUtils) {
274
        Path path = null;
275
        try {
276
            path = fileUtils.safeResolve(parentDirectory, filePath);
277
        } catch (IOException e) {
278
            throw new WebServerException("Error at Response.buildLargeFileResponse", e);
279
        }
280 1 1. buildLargeFileResponse : replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED
        return buildLargeFileResponse(extraHeaders, path.toString(), requestHeaders, fileUtils);
281
    }
282
283
    /**
284
     * This will send a large file to the client as a stream.
285
     * This is a safe version of the method, for when the file path is set by a user, meaning it is untrusted.
286
     * By setting a parentDirectory, the system will ensure that the requested path is within that directory, thus
287
     * disallowing path strings that could escape it, like prefixing with a slash.
288
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
289
     *                     key is the header key and the value is the header value. No colon necessary.
290
     *                     For example "Content-Type: video/mp4" or "Content-Type: application/zip"
291
     * @param filePath the string value of the path to this file. <em>Caution! if this is set by the user.  See notes above</em>
292
     * @param requestHeaders these are the headers from the request, which is needed here to set proper
293
     *                       response headers in some cases.
294
     * @param parentDirectory this value is presumed to be set by the developer, and thus isn't checked for safety. This
295
     *                        allows the developer to use directories anywhere in the system.
296
     */
297
    public static IResponse buildLargeFileResponse(Map<String,String> extraHeaders, String filePath, String parentDirectory, Headers requestHeaders, IFileUtils fileUtils) {
298 1 1. buildLargeFileResponse : replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED
        return buildLargeFileResponse(convertMapToHeaders(extraHeaders), filePath, parentDirectory, requestHeaders, fileUtils);
299
    }
300
301
    /**
302
     * Build a {@link Response} with just a status code and headers, without a body
303
     * @param extraHeaders extra HTTP headers
304
     */
305
    public static IResponse buildLeanResponse(StatusLine.StatusCode statusCode, Headers extraHeaders) {
306 1 1. buildLeanResponse : replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → KILLED
        return new Response(statusCode, extraHeaders, null, socketWrapper -> {}, 0, false);
307
    }
308
309
    /**
310
     * Build a {@link Response} with just a status code and headers, without a body
311
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
312
     *                     key is the header key and the value is the header value. No colon necessary.
313
     */
314
    public static IResponse buildLeanResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders) {
315 1 1. buildLeanResponse : replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → KILLED
        return buildLeanResponse(statusCode, convertMapToHeaders(extraHeaders));
316
    }
317
318
    /**
319
     * Build a {@link Response} with only a status code, with no body and no extra headers.
320
     */
321
    public static IResponse buildLeanResponse(StatusLine.StatusCode statusCode) {
322 1 1. buildLeanResponse : replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → KILLED
        return new Response(statusCode, Headers.EMPTY, null, socketWrapper -> {}, 0, false);
323
    }
324
325
    /**
326
     * A helper method to create a response that returns a
327
     * 303 status code ("see other").  Provide a url that will
328
     * be handed to the browser.  This url may be relative or absolute.
329
     */
330
    public static IResponse redirectTo(String locationUrl) {
331
        try {
332
            // check if we can create a URL from this - any failures indicate invalid syntax.
333
            URI.create(locationUrl);
334
        } catch (Exception ex) {
335
            throw new WebServerException("Failure in redirect to (" + locationUrl + "). Exception: " + ex);
336
        }
337 1 1. redirectTo : replaced return value with null for com/renomad/minum/web/Response::redirectTo → KILLED
        return buildResponse(StatusLine.StatusCode.CODE_303_SEE_OTHER, new Headers(List.of("location: " + locationUrl, "Content-Type: text/html; charset=UTF-8")), "<p>See <a href=\""+locationUrl+"\">this link</a></p>");
338
    }
339
340
    /**
341
     * If you are returning HTML text with a 200 ok, this is a helper that
342
     * lets you skip some of the boilerplate. This version of the helper
343
     * lets you add extra headers on top of the basic content-type headers
344
     * that are needed to specify this is HTML.
345
     */
346
    public static IResponse htmlOk(String body, Headers extraHeaders) {
347
        List<String> headersList = new ArrayList<>(extraHeaders.getHeaderStrings());
348
        headersList.add("Content-Type: text/html; charset=UTF-8");
349
        var headers =  new Headers(headersList);
350 1 1. htmlOk : replaced return value with null for com/renomad/minum/web/Response::htmlOk → KILLED
        return buildResponse(StatusLine.StatusCode.CODE_200_OK, headers, body);
351
    }
352
353
    /**
354
     * If you are returning HTML text with a 200 ok, this is a helper that
355
     * lets you skip some of the boilerplate.
356
     */
357
    public static IResponse htmlOk(String body) {
358 1 1. htmlOk : replaced return value with null for com/renomad/minum/web/Response::htmlOk → KILLED
        return htmlOk(body, Headers.EMPTY);
359
    }
360
361
    @Override
362
    public Headers getExtraHeaders() {
363 1 1. getExtraHeaders : replaced return value with null for com/renomad/minum/web/Response::getExtraHeaders → KILLED
        return new Headers(this.extraHeaders.getHeaderStrings());
364
    }
365
366
    @Override
367
    public StatusLine.StatusCode getStatusCode() {
368 1 1. getStatusCode : replaced return value with null for com/renomad/minum/web/Response::getStatusCode → KILLED
        return statusCode;
369
    }
370
371
    @Override
372
    public boolean isBodyText() {
373 2 1. isBodyText : replaced boolean return with false for com/renomad/minum/web/Response::isBodyText → TIMED_OUT
2. isBodyText : replaced boolean return with true for com/renomad/minum/web/Response::isBodyText → KILLED
        return isBodyText;
374
    }
375
376
    @Override
377
    public long getBodyLength() {
378 1 1. getBodyLength : negated conditional → KILLED
        if (body != null) {
379 1 1. getBodyLength : replaced long return with 0 for com/renomad/minum/web/Response::getBodyLength → KILLED
            return body.length;
380
        } else {
381 1 1. getBodyLength : replaced long return with 0 for com/renomad/minum/web/Response::getBodyLength → KILLED
            return bodyLength;
382
        }
383
    }
384
385
    @Override
386
    public void sendBody(ISocketWrapper sw) throws Exception {
387 1 1. sendBody : removed call to com/renomad/minum/web/ThrowingConsumer::accept → KILLED
        outputGenerator.accept(sw);
388
    }
389
390
    /**
391
     * put bytes from a file into the socket, sending to the client
392
     * @param fileChannel the file we are reading from, based on a {@link RandomAccessFile}
393
     * @param length the number of bytes to send.  May be less than the full length of this {@link FileChannel}
394
     */
395
    private static void sendFileChannelResponse(ISocketWrapper sw, FileChannel fileChannel, long length) throws IOException {
396
        try {
397
            int bufferSize = 8 * 1024;
398
            ByteBuffer buff = ByteBuffer.allocate(bufferSize);
399
            long countBytesLeftToSend = length;
400
            while (true) {
401
                int countBytesRead = fileChannel.read(buff);
402 2 1. sendFileChannelResponse : changed conditional boundary → KILLED
2. sendFileChannelResponse : negated conditional → KILLED
                if (countBytesRead <= 0) {
403
                    break;
404
                } else {
405 2 1. sendFileChannelResponse : negated conditional → KILLED
2. sendFileChannelResponse : changed conditional boundary → KILLED
                    if (countBytesLeftToSend < countBytesRead) {
406 1 1. sendFileChannelResponse : removed call to com/renomad/minum/web/ISocketWrapper::send → TIMED_OUT
                        sw.send(buff.array(), 0, (int)countBytesLeftToSend);
407
                        break;
408
                    } else {
409 1 1. sendFileChannelResponse : removed call to com/renomad/minum/web/ISocketWrapper::send → KILLED
                        sw.send(buff.array(), 0, countBytesRead);
410
                    }
411
                    buff.clear();
412
                }
413 1 1. sendFileChannelResponse : Replaced long subtraction with addition → SURVIVED
                countBytesLeftToSend -= countBytesRead;
414
            }
415
        } finally {
416 1 1. sendFileChannelResponse : removed call to java/nio/channels/FileChannel::close → KILLED
            fileChannel.close();
417
        }
418
    }
419
420
    static void sendByteArrayResponse(ISocketWrapper sw, byte[] body) throws IOException{
421 1 1. sendByteArrayResponse : removed call to com/renomad/minum/web/ISocketWrapper::send → KILLED
        sw.send(body);
422
    }
423
424
    @Override
425
    public byte[] getBody() {
426 1 1. getBody : replaced return value with null for com/renomad/minum/web/Response::getBody → KILLED
        return body;
427
    }
428
429
430
431
    @Override
432
    public boolean equals(Object o) {
433 2 1. equals : replaced boolean return with false for com/renomad/minum/web/Response::equals → KILLED
2. equals : negated conditional → KILLED
        if (this == o) return true;
434 3 1. equals : negated conditional → KILLED
2. equals : replaced boolean return with true for com/renomad/minum/web/Response::equals → KILLED
3. equals : negated conditional → KILLED
        if (o == null || getClass() != o.getClass()) return false;
435
        Response response = (Response) o;
436 6 1. equals : replaced boolean return with true for com/renomad/minum/web/Response::equals → KILLED
2. equals : negated conditional → KILLED
3. equals : negated conditional → KILLED
4. equals : negated conditional → KILLED
5. equals : negated conditional → KILLED
6. equals : negated conditional → KILLED
        return bodyLength == response.bodyLength && isBodyText == response.isBodyText && statusCode == response.statusCode && Objects.equals(extraHeaders, response.extraHeaders) && Arrays.equals(body, response.body);
437
    }
438
439
    @Override
440
    public int hashCode() {
441
        int result = Objects.hash(statusCode, extraHeaders, bodyLength, isBodyText);
442 2 1. hashCode : Replaced integer addition with subtraction → SURVIVED
2. hashCode : Replaced integer multiplication with division → KILLED
        result = 31 * result + Arrays.hashCode(body);
443 1 1. hashCode : replaced int return with 0 for com/renomad/minum/web/Response::hashCode → KILLED
        return result;
444
    }
445
446
    @Override
447
    public String toString() {
448 1 1. toString : replaced return value with "" for com/renomad/minum/web/Response::toString → KILLED
        return "Response{" +
449
                "statusCode=" + statusCode +
450
                ", extraHeaders=" + extraHeaders +
451
                ", bodyLength=" + bodyLength +
452
                ", isBodyText=" + isBodyText +
453
                '}';
454
    }
455
}

Mutations

90

1.1
Location : <init>
Killed by : com.renomad.minum.web.EndpointTests
negated conditional → KILLED

91

1.1
Location : <init>
Killed by : com.renomad.minum.web.EndpointTests
negated conditional → KILLED

109

1.1
Location : buildStreamingResponse
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED

122

1.1
Location : buildStreamingResponse
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED

134

1.1
Location : buildStreamingResponse
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED

147

1.1
Location : buildStreamingResponse
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED

161

1.1
Location : lambda$buildResponse$0
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
removed call to com/renomad/minum/web/Response::sendByteArrayResponse → KILLED

2.2
Location : buildResponse
Killed by : com.renomad.minum.web.WebFrameworkTests
replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED

176

1.1
Location : buildResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED

180

1.1
Location : convertMapToHeaders
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::convertMapToHeaders → KILLED

2.2
Location : convertMapToHeaders
Killed by : com.renomad.minum.FunctionalTests.test_PathFunction_Response(com.renomad.minum.FunctionalTests)
negated conditional → KILLED

181

1.1
Location : convertMapToHeaders
Killed by : com.renomad.minum.web.WebFrameworkTests
replaced return value with null for com/renomad/minum/web/Response::convertMapToHeaders → KILLED

2.2
Location : lambda$convertMapToHeaders$1
Killed by : com.renomad.minum.web.WebFrameworkTests
replaced return value with "" for com/renomad/minum/web/Response::lambda$convertMapToHeaders$1 → KILLED

190

1.1
Location : buildResponse
Killed by : com.renomad.minum.web.EndpointTests
replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED

2.2
Location : lambda$buildResponse$2
Killed by : com.renomad.minum.web.ResponseTests
removed call to com/renomad/minum/web/Response::sendByteArrayResponse → KILLED

199

1.1
Location : buildResponse
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED

215

1.1
Location : buildLargeFileResponse
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED

239

1.1
Location : buildLargeFileResponse
Killed by : com.renomad.minum.web.ResponseTests
negated conditional → KILLED

242

1.1
Location : buildLargeFileResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
Replaced long subtraction with addition → KILLED

2.2
Location : buildLargeFileResponse
Killed by : none
Replaced long addition with subtraction → SURVIVED
Covering tests

251

1.1
Location : lambda$buildLargeFileResponse$3
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
removed call to java/io/RandomAccessFile::seek → KILLED

253

1.1
Location : lambda$buildLargeFileResponse$3
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
removed call to com/renomad/minum/web/Response::sendFileChannelResponse → KILLED

257

1.1
Location : buildLargeFileResponse
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED

280

1.1
Location : buildLargeFileResponse
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED

298

1.1
Location : buildLargeFileResponse
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED

306

1.1
Location : buildLeanResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → KILLED

315

1.1
Location : buildLeanResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → KILLED

322

1.1
Location : buildLeanResponse
Killed by : com.renomad.minum.web.WebFrameworkTests
replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → KILLED

337

1.1
Location : redirectTo
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
replaced return value with null for com/renomad/minum/web/Response::redirectTo → KILLED

350

1.1
Location : htmlOk
Killed by : com.renomad.minum.web.EndpointTests
replaced return value with null for com/renomad/minum/web/Response::htmlOk → KILLED

358

1.1
Location : htmlOk
Killed by : com.renomad.minum.web.EndpointTests
replaced return value with null for com/renomad/minum/web/Response::htmlOk → KILLED

363

1.1
Location : getExtraHeaders
Killed by : com.renomad.minum.web.BodyProcessorTests
replaced return value with null for com/renomad/minum/web/Response::getExtraHeaders → KILLED

368

1.1
Location : getStatusCode
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with null for com/renomad/minum/web/Response::getStatusCode → KILLED

373

1.1
Location : isBodyText
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
replaced boolean return with true for com/renomad/minum/web/Response::isBodyText → KILLED

2.2
Location : isBodyText
Killed by : none
replaced boolean return with false for com/renomad/minum/web/Response::isBodyText → TIMED_OUT

378

1.1
Location : getBodyLength
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
negated conditional → KILLED

379

1.1
Location : getBodyLength
Killed by : com.renomad.minum.web.BodyProcessorTests
replaced long return with 0 for com/renomad/minum/web/Response::getBodyLength → KILLED

381

1.1
Location : getBodyLength
Killed by : com.renomad.minum.web.WebTests
replaced long return with 0 for com/renomad/minum/web/Response::getBodyLength → KILLED

387

1.1
Location : sendBody
Killed by : com.renomad.minum.web.ResponseTests
removed call to com/renomad/minum/web/ThrowingConsumer::accept → KILLED

402

1.1
Location : sendFileChannelResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
changed conditional boundary → KILLED

2.2
Location : sendFileChannelResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
negated conditional → KILLED

405

1.1
Location : sendFileChannelResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
negated conditional → KILLED

2.2
Location : sendFileChannelResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
changed conditional boundary → KILLED

406

1.1
Location : sendFileChannelResponse
Killed by : none
removed call to com/renomad/minum/web/ISocketWrapper::send → TIMED_OUT

409

1.1
Location : sendFileChannelResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
removed call to com/renomad/minum/web/ISocketWrapper::send → KILLED

413

1.1
Location : sendFileChannelResponse
Killed by : none
Replaced long subtraction with addition → SURVIVED
Covering tests

416

1.1
Location : sendFileChannelResponse
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
removed call to java/nio/channels/FileChannel::close → KILLED

421

1.1
Location : sendByteArrayResponse
Killed by : com.renomad.minum.web.ResponseTests
removed call to com/renomad/minum/web/ISocketWrapper::send → KILLED

426

1.1
Location : getBody
Killed by : com.renomad.minum.web.EndpointTests
replaced return value with null for com/renomad/minum/web/Response::getBody → KILLED

433

1.1
Location : equals
Killed by : com.renomad.minum.EqualsTests.equalsTest(com.renomad.minum.EqualsTests)
replaced boolean return with false for com/renomad/minum/web/Response::equals → KILLED

2.2
Location : equals
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

434

1.1
Location : equals
Killed by : com.renomad.minum.web.ResponseTests
negated conditional → KILLED

2.2
Location : equals
Killed by : com.renomad.minum.EqualsTests.equalsTest(com.renomad.minum.EqualsTests)
replaced boolean return with true for com/renomad/minum/web/Response::equals → KILLED

3.3
Location : equals
Killed by : com.renomad.minum.web.ResponseTests
negated conditional → KILLED

436

1.1
Location : equals
Killed by : com.renomad.minum.web.WebTests
replaced boolean return with true for com/renomad/minum/web/Response::equals → KILLED

2.2
Location : equals
Killed by : com.renomad.minum.web.ResponseTests
negated conditional → KILLED

3.3
Location : equals
Killed by : com.renomad.minum.web.ResponseTests
negated conditional → KILLED

4.4
Location : equals
Killed by : com.renomad.minum.web.ResponseTests
negated conditional → KILLED

5.5
Location : equals
Killed by : com.renomad.minum.web.ResponseTests
negated conditional → KILLED

6.6
Location : equals
Killed by : com.renomad.minum.web.ResponseTests
negated conditional → KILLED

442

1.1
Location : hashCode
Killed by : none
Replaced integer addition with subtraction → SURVIVED
Covering tests

2.2
Location : hashCode
Killed by : com.renomad.minum.EqualsTests.equalsTest(com.renomad.minum.EqualsTests)
Replaced integer multiplication with division → KILLED

443

1.1
Location : hashCode
Killed by : com.renomad.minum.EqualsTests.equalsTest(com.renomad.minum.EqualsTests)
replaced int return with 0 for com/renomad/minum/web/Response::hashCode → KILLED

448

1.1
Location : toString
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with "" for com/renomad/minum/web/Response::toString → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0