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
    public 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
     * @deprecated analysis showed that this will return a content-length of zero, thus will
108
     * malfunction when used with clients.  See AIFindingsWebTests#test_Finding_StreamingResponseAdvertisesZeroContentLength
109
     */
110
    @Deprecated
111
    public static IResponse buildStreamingResponse(StatusLine.StatusCode statusCode, Headers extraHeaders, ThrowingConsumer<ISocketWrapper> outputGenerator) {
112 1 1. buildStreamingResponse : replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED
        return new Response(statusCode, extraHeaders, null, outputGenerator, 0L, false);
113
    }
114
115
    /**
116
     * This factory method is intended for situations where the user wishes to stream data
117
     * but lacks the content length.  This is only for unusual situations where the developer
118
     * needs the extra control.  In most cases, other methods are more suitable.
119
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
120
     *                     key is the header key and the value is the header value. No colon necessary.
121
     * @param outputGenerator a function that will be given a {@link ISocketWrapper}, providing the
122
     *                        ability to send bytes on the socket.
123
     * @deprecated analysis showed that this will return a content-length of zero, thus will
124
     * malfunction when used with clients.  See AIFindingsWebTests#test_Finding_StreamingResponseAdvertisesZeroContentLength
125
     */
126
    @Deprecated
127
    public static IResponse buildStreamingResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders, ThrowingConsumer<ISocketWrapper> outputGenerator) {
128 1 1. buildStreamingResponse : replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED
        return buildStreamingResponse(statusCode, convertMapToHeaders(extraHeaders), outputGenerator);
129
    }
130
131
    /**
132
     * Similar to {@link Response#buildStreamingResponse(StatusLine.StatusCode, Headers, ThrowingConsumer)} but here we know
133
     * the body length, so that will be sent to the client as content-length.
134
     * @param extraHeaders any extra headers for the response, such as the content-type
135
     * @param outputGenerator a function that will be given a {@link ISocketWrapper}, providing the
136
     *                        ability to send bytes on the socket.
137
     * @param bodyLength the length, in bytes, of the data to be sent to the client
138
     */
139
    public static IResponse buildStreamingResponse(StatusLine.StatusCode statusCode, Headers extraHeaders, ThrowingConsumer<ISocketWrapper> outputGenerator, long bodyLength) {
140 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);
141
    }
142
143
    /**
144
     * Similar to {@link Response#buildStreamingResponse(StatusLine.StatusCode, Headers, ThrowingConsumer)} but here we know
145
     * the body length, so that will be sent to the client as content-length.
146
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
147
     *                     key is the header key and the value is the header value. No colon necessary.
148
     * @param outputGenerator a function that will be given a {@link ISocketWrapper}, providing the
149
     *                        ability to send bytes on the socket.
150
     * @param bodyLength the length, in bytes, of the data to be sent to the client
151
     */
152
    public static IResponse buildStreamingResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders, ThrowingConsumer<ISocketWrapper> outputGenerator, long bodyLength) {
153 1 1. buildStreamingResponse : replaced return value with null for com/renomad/minum/web/Response::buildStreamingResponse → KILLED
        return buildStreamingResponse(statusCode, convertMapToHeaders(extraHeaders), outputGenerator, bodyLength);
154
    }
155
156
    /**
157
     * A constructor for situations where the developer wishes to send a small (less than a megabyte) byte array
158
     * to the client.  If there is need to send something of larger size, choose one these
159
     * alternate constructors:
160
     * FileChannel - for sending a large file: {@link Response#buildLargeFileResponse(Headers, String, String, Headers, IFileUtils)}
161
     * Streaming - for more custom streaming control with a known body size: {@link Response#buildStreamingResponse(StatusLine.StatusCode, Map, ThrowingConsumer, long)}
162
     * Streaming - for more custom streaming control with body size unknown: {@link Response#buildStreamingResponse(StatusLine.StatusCode, Map, ThrowingConsumer)}
163
     *
164
     * @param extraHeaders any extra headers for the response, such as the content-type.
165
     */
166
    public static IResponse buildResponse(StatusLine.StatusCode statusCode, Headers extraHeaders, byte[] body) {
167 2 1. lambda$buildResponse$0 : removed call to com/renomad/minum/web/ISocketWrapper::send → KILLED
2. buildResponse : replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED
        return new Response(statusCode, extraHeaders, body, socketWrapper -> socketWrapper.send(body), body.length, false);
168
    }
169
170
    /**
171
     * A constructor for situations where the developer wishes to send a small (less than a megabyte) byte array
172
     * to the client.  If there is need to send something of larger size, choose one these
173
     * alternate constructors:
174
     * FileChannel - for sending a large file: {@link Response#buildLargeFileResponse(Headers, String, String, Headers, IFileUtils)}
175
     * Streaming - for more custom streaming control with a known body size: {@link Response#buildStreamingResponse(StatusLine.StatusCode, Headers, ThrowingConsumer, long)}
176
     * Streaming - for more custom streaming control with body size unknown: {@link Response#buildStreamingResponse(StatusLine.StatusCode, Headers, ThrowingConsumer)}
177
     *
178
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
179
     *                     key is the header key and the value is the header value. No colon necessary.
180
     */
181
    public static IResponse buildResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders, byte[] body) {
182 1 1. buildResponse : replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED
        return buildResponse(statusCode, convertMapToHeaders(extraHeaders), body);
183
    }
184
185
    private static Headers convertMapToHeaders(Map<String, String> extraHeaders) {
186 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;
187 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());
188
    }
189
190
    /**
191
     * Build an ordinary response, with a known body
192
     * @param extraHeaders extra HTTP headers, like <pre>content-type: text/html</pre>
193
     */
194
    public static IResponse buildResponse(StatusLine.StatusCode statusCode, Headers extraHeaders, String body) {
195
        byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
196 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/ISocketWrapper::send → KILLED
        return new Response(statusCode, extraHeaders, bytes, socketWrapper -> socketWrapper.send(bytes), bytes.length, true);
197
    }
198
199
    /**
200
     * Build an ordinary response, with a known body
201
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
202
     *                     key is the header key and the value is the header value. No colon necessary.
203
     */
204
    public static IResponse buildResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders, String body) {
205 1 1. buildResponse : replaced return value with null for com/renomad/minum/web/Response::buildResponse → KILLED
        return buildResponse(statusCode, convertMapToHeaders(extraHeaders), body);
206
    }
207
208
209
    /**
210
     * This will send a large file to the client as a stream.
211
     * <em>Note that this does not check that the requested file is within a directory. If you expect the
212
     * path value to be untrusted - that is, set by a user - use {@link #buildLargeFileResponse(Headers, String, String, Headers, IFileUtils)}</em>
213
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
214
     *                     key is the header key and the value is the header value. No colon necessary.
215
     *                     For example "Content-Type: video/mp4" or "Content-Type: application/zip"
216
     * @param filePath the string value of the path to this file. <em>Caution! if this is set by the user.  See notes above</em>
217
     * @param requestHeaders these are the headers from the request, which is needed here to set proper
218
     *                       response headers in some cases.
219
     */
220
    public static IResponse buildLargeFileResponse(Map<String,String> extraHeaders, String filePath, Headers requestHeaders, IFileUtils fileUtils) {
221 1 1. buildLargeFileResponse : replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED
        return buildLargeFileResponse(convertMapToHeaders(extraHeaders), filePath, requestHeaders, fileUtils);
222
    }
223
224
    /**
225
     * This will send a large file to the client as a stream.
226
     * <em>Note that this does not check that the requested file is within a directory. If you expect the
227
     * path value to be untrusted - that is, set by a user - use {@link #buildLargeFileResponse(Headers, String, String, Headers, IFileUtils)}</em>
228
     * @param extraHeaders extra headers you would like in the response, as key-value pairs in a map - for
229
     *                     example "Content-Type: video/mp4" or "Content-Type: application/zip"
230
     * @param filePath the string value of the path to this file. <em>Caution! if this is set by the user.  See notes above</em>
231
     * @param requestHeaders these are the headers from the request, which is needed here to set proper
232
     *                       response headers in some cases.
233
     */
234
    public static IResponse buildLargeFileResponse(Headers extraHeaders, String filePath, Headers requestHeaders, IFileUtils fileUtils) {
235
        Headers adjustedHeaders = extraHeaders;
236
        long fileSize;
237
        try {
238
            fileSize = fileUtils.size(Path.of(filePath));
239
        } catch (IOException e) {
240
            throw new WebServerException("Error in Response.buildLargeFileResponse", e);
241
        }
242
        var range = new Range(requestHeaders, fileSize);
243
        StatusLine.StatusCode responseCode = CODE_200_OK;
244
        long length = fileSize;
245 1 1. buildLargeFileResponse : negated conditional → KILLED
        if (range.hasRangeHeader()) {
246
            long offset = range.getOffset();
247
            length = range.getLength();
248 2 1. buildLargeFileResponse : Replaced long subtraction with addition → KILLED
2. buildLargeFileResponse : Replaced long addition with subtraction → KILLED
            var lastIndex = (offset + length) - 1;
249
            List<String> headerStrings = new ArrayList<>(extraHeaders.getHeaderStrings());
250
            headerStrings.add("Content-Range: " + String.format("bytes %d-%d/%d", offset, lastIndex, fileSize));
251
            adjustedHeaders = new Headers(headerStrings);
252
            responseCode = CODE_206_PARTIAL_CONTENT;
253
        }
254
255
        ThrowingConsumer<ISocketWrapper> outputGenerator = socketWrapper -> {
256
            try (RandomAccessFile reader = new RandomAccessFile(filePath, "r")) {
257 1 1. lambda$buildLargeFileResponse$3 : removed call to java/io/RandomAccessFile::seek → KILLED
                reader.seek(range.getOffset());
258
                var fileChannel = reader.getChannel();
259 1 1. lambda$buildLargeFileResponse$3 : removed call to com/renomad/minum/web/Response::sendFileChannelResponse → KILLED
                sendFileChannelResponse(socketWrapper, fileChannel, range.getLength());
260
            }
261
        };
262
263 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);
264
    }
265
266
    /**
267
     * This will send a large file to the client as a stream.
268
     * This is a safe version of the method, for when the file path is set by a user, meaning it is untrusted.
269
     * By setting a parentDirectory, the system will ensure that the requested path is within that directory, thus
270
     * disallowing path strings that could escape it, like prefixing with a slash.
271
     * @param extraHeaders extra headers you would like in the response, as key-value pairs in a map - for
272
     *                     example "Content-Type: video/mp4" or "Content-Type: application/zip"
273
     * @param filePath the string value of the path to this file. <em>Caution! if this is set by the user.  See notes above</em>
274
     * @param requestHeaders these are the headers from the request, which is needed here to set proper
275
     *                       response headers in some cases.
276
     * @param parentDirectory this value is presumed to be set by the developer, and thus isn't checked for safety. This
277
     *                        allows the developer to use directories anywhere in the system.
278
     */
279
    public static IResponse buildLargeFileResponse(Headers extraHeaders, String filePath, String parentDirectory, Headers requestHeaders, IFileUtils fileUtils) {
280
        Path path;
281
        try {
282
            path = fileUtils.safeResolve(parentDirectory, filePath);
283
        } catch (IOException e) {
284
            throw new WebServerException("Error at Response.buildLargeFileResponse", e);
285
        }
286 1 1. buildLargeFileResponse : replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED
        return buildLargeFileResponse(extraHeaders, path.toString(), requestHeaders, fileUtils);
287
    }
288
289
    /**
290
     * This will send a large file to the client as a stream.
291
     * This is a safe version of the method, for when the file path is set by a user, meaning it is untrusted.
292
     * By setting a parentDirectory, the system will ensure that the requested path is within that directory, thus
293
     * disallowing path strings that could escape it, like prefixing with a slash.
294
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
295
     *                     key is the header key and the value is the header value. No colon necessary.
296
     *                     For example "Content-Type: video/mp4" or "Content-Type: application/zip"
297
     * @param filePath the string value of the path to this file. <em>Caution! if this is set by the user.  See notes above</em>
298
     * @param requestHeaders these are the headers from the request, which is needed here to set proper
299
     *                       response headers in some cases.
300
     * @param parentDirectory this value is presumed to be set by the developer, and thus isn't checked for safety. This
301
     *                        allows the developer to use directories anywhere in the system.
302
     */
303
    public static IResponse buildLargeFileResponse(Map<String,String> extraHeaders, String filePath, String parentDirectory, Headers requestHeaders, IFileUtils fileUtils) {
304 1 1. buildLargeFileResponse : replaced return value with null for com/renomad/minum/web/Response::buildLargeFileResponse → KILLED
        return buildLargeFileResponse(convertMapToHeaders(extraHeaders), filePath, parentDirectory, requestHeaders, fileUtils);
305
    }
306
307
    /**
308
     * Build a {@link Response} with just a status code and headers, without a body
309
     * @param extraHeaders extra HTTP headers
310
     */
311
    public static IResponse buildLeanResponse(StatusLine.StatusCode statusCode, Headers extraHeaders) {
312 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);
313
    }
314
315
    /**
316
     * Build a {@link Response} with just a status code and headers, without a body
317
     * @param extraHeaders any extra headers for the response, such as the content-type, as a map, where the
318
     *                     key is the header key and the value is the header value. No colon necessary.
319
     */
320
    public static IResponse buildLeanResponse(StatusLine.StatusCode statusCode, Map<String,String> extraHeaders) {
321 1 1. buildLeanResponse : replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → KILLED
        return buildLeanResponse(statusCode, convertMapToHeaders(extraHeaders));
322
    }
323
324
    /**
325
     * Build a {@link Response} with only a status code, with no body and no extra headers.
326
     */
327
    public static IResponse buildLeanResponse(StatusLine.StatusCode statusCode) {
328 1 1. buildLeanResponse : replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → TIMED_OUT
        return new Response(statusCode, Headers.EMPTY, null, socketWrapper -> {}, 0, false);
329
    }
330
331
    /**
332
     * A helper method to create a response that returns a
333
     * 303 status code ("see other").  Provide a url that will
334
     * be handed to the browser.  This url may be relative or absolute.
335
     */
336
    public static IResponse redirectTo(String locationUrl) {
337
        try {
338
            // check if we can create a URL from this - any failures indicate invalid syntax.
339
            URI.create(locationUrl);
340
        } catch (Exception ex) {
341
            throw new WebServerException("Failure in redirect to (" + locationUrl + "). Exception: " + ex);
342
        }
343 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>");
344
    }
345
346
    /**
347
     * If you are returning HTML text with a 200 ok, this is a helper that
348
     * lets you skip some of the boilerplate. This version of the helper
349
     * lets you add extra headers on top of the basic content-type headers
350
     * that are needed to specify this is HTML.
351
     */
352
    public static IResponse htmlOk(String body, Headers extraHeaders) {
353
        List<String> headersList = new ArrayList<>(extraHeaders.getHeaderStrings());
354
        headersList.add("Content-Type: text/html; charset=UTF-8");
355
        var headers =  new Headers(headersList);
356 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);
357
    }
358
359
    /**
360
     * If you are returning HTML text with a 200 ok, this is a helper that
361
     * lets you skip some of the boilerplate.
362
     */
363
    public static IResponse htmlOk(String body) {
364 1 1. htmlOk : replaced return value with null for com/renomad/minum/web/Response::htmlOk → KILLED
        return htmlOk(body, Headers.EMPTY);
365
    }
366
367
    @Override
368
    public Headers getExtraHeaders() {
369 1 1. getExtraHeaders : replaced return value with null for com/renomad/minum/web/Response::getExtraHeaders → KILLED
        return new Headers(this.extraHeaders.getHeaderStrings());
370
    }
371
372
    @Override
373
    public StatusLine.StatusCode getStatusCode() {
374 1 1. getStatusCode : replaced return value with null for com/renomad/minum/web/Response::getStatusCode → KILLED
        return statusCode;
375
    }
376
377
    @Override
378
    public boolean isBodyText() {
379 2 1. isBodyText : replaced boolean return with true for com/renomad/minum/web/Response::isBodyText → TIMED_OUT
2. isBodyText : replaced boolean return with false for com/renomad/minum/web/Response::isBodyText → KILLED
        return isBodyText;
380
    }
381
382
    @Override
383
    public long getBodyLength() {
384 1 1. getBodyLength : negated conditional → KILLED
        if (body != null) {
385 1 1. getBodyLength : replaced long return with 0 for com/renomad/minum/web/Response::getBodyLength → KILLED
            return body.length;
386
        } else {
387 1 1. getBodyLength : replaced long return with 0 for com/renomad/minum/web/Response::getBodyLength → KILLED
            return bodyLength;
388
        }
389
    }
390
391
    @Override
392
    public void sendBody(ISocketWrapper sw) throws Exception {
393 1 1. sendBody : removed call to com/renomad/minum/web/ThrowingConsumer::accept → KILLED
        outputGenerator.accept(sw);
394
    }
395
396
    /**
397
     * put bytes from a file into the socket, sending to the client
398
     * @param fileChannel the file we are reading from, based on a {@link RandomAccessFile}
399
     * @param length the number of bytes to send.  May be less than the full length of this {@link FileChannel}
400
     */
401
    private static void sendFileChannelResponse(ISocketWrapper sw, FileChannel fileChannel, long length) throws IOException {
402
        try {
403
            int bufferSize = 8 * 1024;
404
            ByteBuffer buff = ByteBuffer.allocate(bufferSize);
405
            long countBytesLeftToSend = length;
406
            while (true) {
407
                int countBytesRead = fileChannel.read(buff);
408 2 1. sendFileChannelResponse : changed conditional boundary → KILLED
2. sendFileChannelResponse : negated conditional → KILLED
                if (countBytesRead <= 0) {
409
                    break;
410
                } else {
411 2 1. sendFileChannelResponse : changed conditional boundary → SURVIVED
2. sendFileChannelResponse : negated conditional → KILLED
                    if (countBytesLeftToSend < (long) countBytesRead) {
412 1 1. sendFileChannelResponse : removed call to com/renomad/minum/web/ISocketWrapper::send → TIMED_OUT
                        sw.send(buff.array(), 0, (int)countBytesLeftToSend);
413
                        break;
414
                    } else {
415 1 1. sendFileChannelResponse : removed call to com/renomad/minum/web/ISocketWrapper::send → TIMED_OUT
                        sw.send(buff.array(), 0, countBytesRead);
416
                    }
417
                    buff.clear();
418
                }
419 1 1. sendFileChannelResponse : Replaced long subtraction with addition → KILLED
                countBytesLeftToSend = countBytesLeftToSend - countBytesRead;
420
            }
421
        } finally {
422 1 1. sendFileChannelResponse : removed call to java/nio/channels/FileChannel::close → KILLED
            fileChannel.close();
423
        }
424
    }
425
426
    @Override
427
    public byte[] getBody() {
428 1 1. getBody : replaced return value with null for com/renomad/minum/web/Response::getBody → KILLED
        return body;
429
    }
430
431
    @Override
432
    public ThrowingConsumer<ISocketWrapper> getOutputGenerator() {
433 1 1. getOutputGenerator : replaced return value with null for com/renomad/minum/web/Response::getOutputGenerator → SURVIVED
        return outputGenerator;
434
    }
435
436
    @Override
437
    public boolean equals(Object o) {
438 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;
439
        Response response = (Response) o;
440 6 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
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) && Objects.deepEquals(body, response.body);
441
    }
442
443
    @Override
444
    public int hashCode() {
445 1 1. hashCode : replaced int return with 0 for com/renomad/minum/web/Response::hashCode → KILLED
        return Objects.hash(statusCode, extraHeaders, Arrays.hashCode(body), bodyLength, isBodyText);
446
    }
447
448
    @Override
449
    public String toString() {
450 1 1. toString : replaced return value with "" for com/renomad/minum/web/Response::toString → KILLED
        return "Response{" +
451
                "statusCode=" + statusCode +
452
                ", extraHeaders=" + extraHeaders +
453
                ", bodyLength=" + bodyLength +
454
                ", isBodyText=" + isBodyText +
455
                '}';
456
    }
457
}

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

112

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

128

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

140

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

153

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

167

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/ISocketWrapper::send → 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

182

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

186

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_Range(com.renomad.minum.FunctionalTests)
negated conditional → KILLED

187

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 : lambda$convertMapToHeaders$1
Killed by : com.renomad.minum.web.ResponseTests
replaced return value with "" for com/renomad/minum/web/Response::lambda$convertMapToHeaders$1 → KILLED

196

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/ISocketWrapper::send → KILLED

205

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

221

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

245

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

248

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 : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
Replaced long addition with subtraction → KILLED

257

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

259

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

263

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

286

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

304

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

312

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

321

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

328

1.1
Location : buildLeanResponse
Killed by : none
replaced return value with null for com/renomad/minum/web/Response::buildLeanResponse → TIMED_OUT

343

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

356

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

364

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

369

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

374

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

379

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

2.2
Location : isBodyText
Killed by : com.renomad.minum.web.ResponseTests
replaced boolean return with false for com/renomad/minum/web/Response::isBodyText → KILLED

384

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

385

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

387

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

393

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

408

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

411

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 : none
changed conditional boundary → SURVIVED
Covering tests

412

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

415

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

419

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

422

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

428

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 : getOutputGenerator
Killed by : none
replaced return value with null for com/renomad/minum/web/Response::getOutputGenerator → SURVIVED
Covering tests

438

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

440

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

2.2
Location : equals
Killed by : com.renomad.minum.web.WebTests
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

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

445

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

450

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