Skip to content

Conversation

@Michael-Mc-Mahon
Copy link
Member

@Michael-Mc-Mahon Michael-Mc-Mahon commented Nov 4, 2025

I'd like to return to this issue with a new PR that addresses the usability issue minimally. Namely to define numeric constants
for the counter-intuitive responseLength values in HttpExchange.sendResponseHeaders. The apidoc then refers to the constant names rather than the numeric values.

I've updated the implementation and some tests (though not all yet) to use the constant names. I'm open to suggestions on the names themselves. Once that is agreed, I'll update the remaining tests and any other call sites in the implementation.

Thanks,
Michael


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires CSR request JDK-8371247 to be approved

Issues

  • JDK-8331195: Improve com.sun.net.httpserver.HttpExchange usability (Enhancement - P4)
  • JDK-8371247: Improve com.sun.net.httpserver.HttpExchange usability (CSR)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/28132/head:pull/28132
$ git checkout pull/28132

Update a local copy of the PR:
$ git checkout pull/28132
$ git pull https://git.openjdk.org/jdk.git pull/28132/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 28132

View PR using the GUI difftool:
$ git pr show -t 28132

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/28132.diff

Using Webrev

Link to Webrev Comment

@Michael-Mc-Mahon
Copy link
Member Author

/csr needed

@bridgekeeper
Copy link

bridgekeeper bot commented Nov 4, 2025

👋 Welcome back michaelm! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Nov 4, 2025

@Michael-Mc-Mahon This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8331195: Improve com.sun.net.httpserver.HttpExchange usability

Reviewed-by: jpai, dfuchs

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 95 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot added the csr Pull request needs approved CSR before integration label Nov 4, 2025
@openjdk
Copy link

openjdk bot commented Nov 4, 2025

@Michael-Mc-Mahon has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@Michael-Mc-Mahon please create a CSR request for issue JDK-8331195 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@openjdk openjdk bot added the net net-dev@openjdk.org label Nov 4, 2025
@openjdk
Copy link

openjdk bot commented Nov 4, 2025

@Michael-Mc-Mahon The following label will be automatically applied to this pull request:

  • net

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the rfr Pull request is ready for review label Nov 4, 2025
@mlbridge
Copy link

mlbridge bot commented Nov 4, 2025

@dfuch
Copy link
Member

dfuch commented Nov 4, 2025

This is a good idea, but it kind of hides the fact that passing 0 may not do what you think it does.

Typically the pitfall is to assume that if you have an array of bytes then you can can call sendResponseHeaders with the array length. The API doc of sendResponseHeaders should probably make it clear that 0 is a special value, and that RSPBODY_CHUNKED is 0.

In addition I would suggest adding code snipets showing both cases (one using a byte array and passing bytes.length == 0 ? RSPBODY_EMPTY : bytes.length and the other using RSPBODY_CHUNKED with e.g. transferring the content of an InputStream.

@Michael-Mc-Mahon
Copy link
Member Author

This is a good idea, but it kind of hides the fact that passing 0 may not do what you think it does.

Typically the pitfall is to assume that if you have an array of bytes then you can can call sendResponseHeaders with the array length. The API doc of sendResponseHeaders should probably make it clear that 0 is a special value, and that RSPBODY_CHUNKED is 0.

Actually, that case is not so bad. What happens is that chunked encoding is selected but the response body
content is empty as intended, and it should work. But, maybe we should still highlight it anyway.

In addition I would suggest adding code snipets showing both cases (one using a byte array and passing bytes.length == 0 ? RSPBODY_EMPTY : bytes.length and the other using RSPBODY_CHUNKED with e.g. transferring the content of an InputStream.

The worse case is expecting a negative value to mean an indefinite (chunked encoded) length. You definitely don't get what you expect in that case.

@Michael-Mc-Mahon
Copy link
Member Author

This is a good idea, but it kind of hides the fact that passing 0 may not do what you think it does.

Typically the pitfall is to assume that if you have an array of bytes then you can can call sendResponseHeaders with the array length. The API doc of sendResponseHeaders should probably make it clear that 0 is a special value, and that RSPBODY_CHUNKED is 0.

In addition I would suggest adding code snipets showing both cases (one using a byte array and passing bytes.length == 0 ? RSPBODY_EMPTY : bytes.length and the other using RSPBODY_CHUNKED with e.g. transferring the content of an InputStream.

I've added an API note. I'd prefer not to drown the whole spec out with code samples just for this issue if possible.

Copy link
Member

@dfuch dfuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the @apiNote and explicitely mentionning the value of RSPBODY_CHUNKED. The new text looks good to me now.

Could be good to double check what would happen if you have something like that:

public void handle(final HttpExchange exchange) throws IOException {
     byte[] bytes = getResponseBody(exchange.getRequestURI());
     long fixedlen = bytes.length == 0 ? RSPBODY_EMPTY : bytes.length;
     exchange.sendResponseHeaders(200, fixedlen);
     try (OutputStream os = exchange.getResponseBody()) {
         os.write(bytes);
     }
}

where getResponseBody(exchange.getRequestURI()); returns new byte[0];
will os.write throw, or will it be no-op?
If it throws then should it be specified that sendResponseHeaders closes the output stream?

@Michael-Mc-Mahon
Copy link
Member Author

Thanks for adding the @apiNote and explicitely mentionning the value of RSPBODY_CHUNKED. The new text looks good to me now.

Could be good to double check what would happen if you have something like that:

public void handle(final HttpExchange exchange) throws IOException {
     byte[] bytes = getResponseBody(exchange.getRequestURI());
     long fixedlen = bytes.length == 0 ? RSPBODY_EMPTY : bytes.length;
     exchange.sendResponseHeaders(200, fixedlen);
     try (OutputStream os = exchange.getResponseBody()) {
         os.write(bytes);
     }
}

where getResponseBody(exchange.getRequestURI()); returns new byte[0]; will os.write throw, or will it be no-op? If it throws then should it be specified that sendResponseHeaders closes the output stream?

That would work fine. What possibly needs to be mentioned is that with chunked encoding, you must always
close the output stream explicitly (or through a try with resources block as in your example). I will add
a sentence to that effect.

Comment on lines 79 to 91
/**
* No response body is being sent with this response
*
* @since 26
*/
public static final long RSPBODY_EMPTY = -1l;

/**
* The response body is unspecified and will be chunk encoded
*
* @since 26
*/
public static final long RSPBODY_CHUNKED = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add:

@see #sendResponseHeaders(int, long)

to make it clearer what these constants are for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add:

@see #sendResponseHeaders(int, long)

to make it clearer what these constants are for?

Good idea

@bowbahdoe
Copy link
Contributor

I've said all my objections before. I think if we waited for member patterns we could have an API that doesn't need the constants and I think that the constants do not solve for the bad patterns today.

#18955

@bowbahdoe
Copy link
Contributor

I think it makes the API very slightly better, but in a way that would detract from future attempts to make the API holistically better to use.

Seriously consider that if we had member patterns would we add this API? If the answer is no (and I think it is no) lets just not do it

@bowbahdoe
Copy link
Contributor

public void handle(final HttpExchange exchange) throws IOException {
     byte[] bytes = getResponseBody(exchange.getRequestURI());
     long fixedlen = bytes.length == 0 ? RSPBODY_EMPTY : bytes.length;
     exchange.sendResponseHeaders(200, fixedlen);
     try (OutputStream os = exchange.getResponseBody()) {
         os.write(bytes);
     }
}

versus

public void handle(final HttpExchange exchange) throws IOException {
     byte[] bytes = getResponseBody(exchange.getRequestURI());
     exchange.sendResponseHeaders(200, ResponseLength.known(bytes));
     try (OutputStream os = exchange.getResponseBody()) {
         os.write(bytes);
     }
}

This has the benefit of us being able to deprecate .sendResponseHeaders(int, long) to guide usage to it. The only reason not to do it today is that we lack the ability to make ResponseLength in the way that is ideal (with known(...) and unknown() having matching extraction patterns)

And in the presence of such an API there are no uses for the constants so...do you see my frustration? You need to know to look for RSPBODY_EMPTY and write bytes.length == 0 ? RSPBODY_EMPTY : bytes.length; This will not lead to a better result.

@dfuch
Copy link
Member

dfuch commented Nov 5, 2025

I don't disagree but adding new APIs is a bigger maintenance cost than adding two simple constants.
I believe the proposed change will improve the usability. The sendResponseHeaders(int, long) cannot be removed, and even if we added a new API it would still be here. There's also the impacts on the SPI to take into account.

@openjdk openjdk bot added ready Pull request is ready to be integrated and removed csr Pull request needs approved CSR before integration labels Nov 5, 2025
@Michael-Mc-Mahon
Copy link
Member Author

I don't agree that you have to go looking for these constants. I think we have to look at it from the point of view of a developer new to the API and someone reading the apidoc for the first time will be clearly drawn to the constant names. I agree a new method with new parameter types would be nicer from an IDE perspective. But if we are going to add a new method then I think we are going to have a wider discussion such as occurred previously with this issue. So, even if we end up doing that and deprecating the present sendResponseHeaders() this effort is not going to get in the way of that.

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Nov 7, 2025
Copy link
Member

@dfuch dfuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Please hold off integrating right now - I've launched a couple of tests.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Nov 7, 2025
@Michael-Mc-Mahon
Copy link
Member Author

LGTM. Please hold off integrating right now - I've launched a couple of tests.

My tier1 and tier2 test run passes

0,
"/", exchange -> {
exchange.sendResponseHeaders(200, 0);
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing that this is intentionally using RSPBODY_EMPTY and previously it was a bug to send 0?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, I just heard the answer to this offline. This looks OK to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Jaikiran. I think 0 was probably not intended. I decided to "fix" it in this place, but leave it in the others

System.out.println("Sending response for request " + exchange.getRequestURI() + " from " + exchange.getRemoteAddress());
reqFinishedProcessing.countDown();
exchange.sendResponseHeaders(200, 0);
exchange.sendResponseHeaders(200, RSPBODY_CHUNKED);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use RSPBODY_EMPTY instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted above, I decided to change it in one place, but leave it as is, in others.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Michael. That's fine with me.

@jaikiran
Copy link
Member

Overall this looks good to me. I just have one question which I've added inline.

@openjdk openjdk bot removed the ready Pull request is ready to be integrated label Nov 10, 2025
Copy link
Member

@jaikiran jaikiran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright year updates look good to me and so does the rest of this PR. I just have one comment here #28132 (comment)

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Nov 10, 2025
@Michael-Mc-Mahon
Copy link
Member Author

/integrate

@openjdk
Copy link

openjdk bot commented Nov 10, 2025

Going to push as commit 1877ff9.
Since your change was applied there have been 97 commits pushed to the master branch:

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Nov 10, 2025
@openjdk openjdk bot closed this Nov 10, 2025
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Nov 10, 2025
@openjdk
Copy link

openjdk bot commented Nov 10, 2025

@Michael-Mc-Mahon Pushed as commit 1877ff9.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integrated Pull request has been integrated net net-dev@openjdk.org

Development

Successfully merging this pull request may close these issues.

4 participants