Skip to content

Commit 00943bf

Browse files
committed
Working
1 parent c1c890f commit 00943bf

File tree

6 files changed

+208
-168
lines changed

6 files changed

+208
-168
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ The general requirements and roadmap are as follows:
231231
### Server tasks
232232

233233
* [x] Basic HTTP 1.1
234+
* [x] Support Accept-Encoding (gzip, deflate)
235+
* [x] Support Content-Encoding (gzip, deflate)
234236
* [x] Support Keep-Alive
235237
* [x] Support Expect-Continue 100
236238
* [x] Support chunked request

src/main/java/io/fusionauth/http/server/HTTPRequest.java

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ public void addAcceptEncodings(List<String> encodings) {
150150
this.acceptEncodings.addAll(encodings);
151151
}
152152

153+
public void addContentEncoding(String encoding) {
154+
this.contentEncodings.add(encoding);
155+
}
156+
157+
public void addContentEncodings(List<String> encodings) {
158+
this.contentEncodings.addAll(encodings);
159+
}
160+
153161
public void addCookies(Cookie... cookies) {
154162
for (Cookie cookie : cookies) {
155163
this.cookies.put(cookie.name, cookie);
@@ -232,10 +240,7 @@ public List<String> getAcceptEncodings() {
232240

233241
public void setAcceptEncodings(List<String> encodings) {
234242
this.acceptEncodings.clear();
235-
// TODO : ? Maybe not worth being defensive here since we likely have many methods on this object that are not null safe?
236-
if (encodings != null) {
237-
this.acceptEncodings.addAll(encodings);
238-
}
243+
this.acceptEncodings.addAll(encodings);
239244
}
240245

241246
/**
@@ -306,13 +311,9 @@ public List<String> getContentEncodings() {
306311
return contentEncodings;
307312
}
308313

309-
// TODO : Note : Should I add 'addContentEncodings' and 'addContentEncoding' to match 'accept*' ?
310314
public void setContentEncodings(List<String> encodings) {
311315
this.contentEncodings.clear();
312-
// TODO : ? Maybe not worth being defensive here since we likely have many methods on this object that are not null safe?
313-
if (encodings != null) {
314-
this.contentEncodings.addAll(encodings);
315-
}
316+
this.contentEncodings.addAll(encodings);
316317
}
317318

318319
public Long getContentLength() {
@@ -751,9 +752,7 @@ private void decodeHeader(String name, String value) {
751752
weight = Double.parseDouble(weightText);
752753
}
753754

754-
// Content-Encoding values are not case-sensitive
755-
// TODO : Ok to lc here? We are currently just always using equalsIgnoreCase
756-
WeightedString ws = new WeightedString(parsed.value().toLowerCase(), weight, index);
755+
WeightedString ws = new WeightedString(parsed.value(), weight, index);
757756
weightedStrings.add(ws);
758757
index++;
759758
}
@@ -778,15 +777,11 @@ private void decodeHeader(String name, String value) {
778777
}
779778
break;
780779
case Headers.ContentEncodingLower:
781-
// TODO : Note that we don't expect more than one Content-Encoding header. We could take the last one we find,
782-
// or try and combine the values. MDN and other places indicate combining may be preferred even though
783-
// it isn't ideal to send multiple headers like this. I tend to think we should just accept the last one.
784780
String[] encodings = value.split(",");
785781
List<String> contentEncodings = new ArrayList<>(1);
786782
int encodingIndex = 0;
787783
for (String encoding : encodings) {
788-
// TODO : Ok to lc here? We are currently just always using equalsIgnoreCase
789-
encoding = encoding.trim().toLowerCase();
784+
encoding = encoding.trim();
790785
if (encoding.isEmpty()) {
791786
continue;
792787
}

src/main/java/io/fusionauth/http/server/internal/HTTPWorker.java

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,6 @@ public void run() {
145145
instrumenter.acceptedRequest();
146146
}
147147

148-
// TODO : Content-Encoding, we are currently keeping the PushbackInputStream for the entire keep-alive session.
149-
// Ideally we'd set the InputStream for gzip, deflate, etc lower.
150-
// Also... with Pushback bytes... the bytes may be compressed for the next request. So we'll need to be able to
151-
// read handle this. So perhaps this should all be handled at a high level?
152-
153-
// HTTPInputStream > Pushback > Decompression > Throughput > Socket
154-
155148
httpInputStream = new HTTPInputStream(configuration, request, inputStream);
156149
request.setInputStream(httpInputStream);
157150

@@ -452,21 +445,19 @@ private Integer validatePreamble(HTTPRequest request) {
452445
request.removeHeader(Headers.ContentLength);
453446
}
454447

455-
// TODO : Note that some indicate you can return a 415 based upon the Accept-Encoding header as well if you do not support the request.
456-
// But I don't know that I agree- to me that is just telling the server here are encoding values I support, and you can tell me what you did.
457-
// The spec even says you can ignore the Accept-Encoding header if you want based upon CPU load, or other reasons.
458-
// Not planning to add any validation for Accept-Encoding.
459-
460448
// Content-Encoding
461449
var contentEncodings = request.getContentEncodings();
462450
// TODO : If provided, I think we can safely remove the Content-Length header if present?
463451
// Although, I suppose as long as we decompress early, we should still be able to use the Content-Length header as long as
464452
// it represents the un-compressed payload.
453+
// Maybe write a test to prove that we can compress with a fixed-length w/ a Content-Length header?
454+
// Current support is for gzip and deflate.
465455
for (var encoding : contentEncodings) {
466-
// We only support gzip and deflate.
467-
// TODO : Ensure we use the same equals check here as other places, I think we should lowercase during parse, and then expect these to be lc.
468-
if (!encoding.equals(ContentEncodings.Gzip) && !encoding.equals(ContentEncodings.Deflate)) {
469-
// TODO : Add log statement
456+
if (!encoding.equalsIgnoreCase(ContentEncodings.Gzip) && !encoding.equalsIgnoreCase(ContentEncodings.Deflate)) {
457+
// Note that while we do not expect multiple Content-Encoding headers, the last one will be used. For good measure,
458+
// use the last one in the debug message as well.
459+
var contentEncodingHeader = request.getHeaders(Headers.ContentEncoding).getLast();
460+
logger.debug("Invalid request. The Content-Type header contains an un-supported value. [{}]", contentEncodingHeader);
470461
return Status.UnsupportedMediaType;
471462
}
472463
}
@@ -489,10 +480,10 @@ private enum CloseSocketReason {
489480
private static class Status {
490481
public static final int BadRequest = 400;
491482

492-
public static final int UnsupportedMediaType = 415;
493-
494483
public static final int HTTPVersionNotSupported = 505;
495484

496485
public static final int InternalServerError = 500;
486+
487+
public static final int UnsupportedMediaType = 415;
497488
}
498489
}

src/main/java/io/fusionauth/http/server/io/HTTPInputStream.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class HTTPInputStream extends InputStream {
5454

5555
private boolean closed;
5656

57-
private boolean committed;
57+
private boolean initialized;
5858

5959
private InputStream delegate;
6060

@@ -141,8 +141,8 @@ public int read(byte[] b, int off, int len) throws IOException {
141141
return -1;
142142
}
143143

144-
if (!committed) {
145-
commit();
144+
if (!initialized) {
145+
initialize();
146146
}
147147

148148
// When we have a fixed length request, read beyond the remainingBytes if possible.
@@ -164,8 +164,8 @@ public int read(byte[] b, int off, int len) throws IOException {
164164
return read;
165165
}
166166

167-
private void commit() throws IOException {
168-
committed = true;
167+
private void initialize() throws IOException {
168+
initialized = true;
169169

170170
Long contentLength = request.getContentLength();
171171
boolean hasBody = (contentLength != null && contentLength > 0) || request.isChunked();
@@ -201,6 +201,17 @@ private void commit() throws IOException {
201201
logger.trace("Client indicated it was NOT sending an entity-body in the request");
202202
}
203203

204+
// Those who push back:
205+
// HTTPInputStream when fixed
206+
// ChunkedInputStream when chunked
207+
// Preamble parser
208+
209+
// HTTPInputStream (this) > Pushback (delegate) > Throughput > Socket
210+
// HTTPInputStream (this) > Chunked (delegate) > Pushback > Throughput > Socket
211+
212+
// HTTPInputStream (this) > Pushback > Decompress > Throughput > Socket
213+
// HTTPInputStream (this) > Pushback > Decompress > Chunked > Throughput > Socket
214+
204215
// TODO : Note I could leave this alone, but when we parse the header we can lower case these values and then remove the equalsIgnoreCase here?
205216
// Seems like ideally we would normalize them to lowercase earlier.
206217
if (hasBody) {

src/main/java/io/fusionauth/http/server/io/HTTPOutputStream.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2024-2025, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -128,9 +128,6 @@ public void reset() {
128128
*/
129129
public boolean willCompress() {
130130
if (compress) {
131-
// TODO : Note I could leave this alone, but when we parse the header we can lower case these values and then remove the equalsIgnoreCase here?
132-
// Seems like ideally we would normalize them to lowercase earlier.
133-
// Hmm.. sems like we have to in theory since someone could call setAcceptEncodings later, or addAcceptEncodings?
134131
for (String encoding : acceptEncodings) {
135132
if (encoding.equalsIgnoreCase(ContentEncodings.Gzip)) {
136133
return true;
@@ -196,9 +193,6 @@ private void commit(boolean closing) throws IOException {
196193
// 204 status is specifically "No Content" so we shouldn't write the content-encoding and vary headers if the status is 204
197194
// TODO : Compress by default is on by default. But it looks like we don't actually compress unless you also send in an Accept-Encoding header?
198195
if (compress && !twoOhFour) {
199-
// TODO : Note I could leave this alone, but when we parse the header we can lower case these values and then remove the equalsIgnoreCase here?
200-
// Seems like ideally we would normalize them to lowercase earlier.
201-
// Hmm.. sems like we have to in theory since someone could call setAcceptEncodings later, or addAcceptEncodings?
202196
for (String encoding : acceptEncodings) {
203197
if (encoding.equalsIgnoreCase(ContentEncodings.Gzip)) {
204198
response.setHeader(Headers.ContentEncoding, ContentEncodings.Gzip);

0 commit comments

Comments
 (0)