Javadoc change in HttpExchange makes external implementations non-complaint
robert engels
rengels at ix.netcom.com
Fri Dec 6 15:19:00 UTC 2024
Sorry, that was incorrect - the api call is on the exchange, but it get/sets the attributes on the related context.
This seems wrong to me as well, and I’ll probably change it in the robaho version, but this would be a breaking change with the JDK implementation.
I agree with Ethan, that I think this should be fixed in the JDK, or at least be marked that the JDK impl is non-compliant.
> On Dec 6, 2024, at 9:15 AM, robert engels <rengels at ix.netcom.com> wrote:
>
> I am confused though - I reviewed the robaho source code which came from the JDK, and the setAttribute is on the exchange, which is per request, not per context.
>
>> On Dec 6, 2024, at 8:41 AM, robert engels <rengels at ix.netcom.com> wrote:
>>
>> In minor defense of the JDK (I am worried more about ambiguity) - I’ve seen the JDK users implement this by add to the request headers - but with the JDK making this read-only, this is no longer possible, so the get/set attribute is the only viable way to pass per request information between layers.
>>
>>> On Dec 5, 2024, at 7:27 PM, Ethan McCue <ethan at mccue.dev> wrote:
>>>
>>> After mulling it over some more, I think that as is there is actually no valid use for .setAttribute as implemented by the JDK
>>>
>>> Even the most trivial usages of it are broken under moderate load. This includes the usage in SimpleFileServer.createOutputFilter and SimpleFileServer.createFileHandler
>>>
>>> import com.sun.net.httpserver.HttpServer;
>>> import com.sun.net.httpserver.SimpleFileServer;
>>>
>>> import java.io.ByteArrayOutputStream;
>>> import java.net.InetSocketAddress;
>>> import java.net.URI;
>>> import java.net.http.HttpClient;
>>> import java.net.http.HttpRequest;
>>> import java.net.http.HttpResponse;
>>> import java.nio.charset.StandardCharsets;
>>> import java.nio.file.Files;
>>> import java.nio.file.Path;
>>> import java.util.concurrent.Executors;
>>> import java.util.concurrent.atomic.AtomicInteger;
>>> import java.util.regex.Pattern;
>>>
>>> public class Main {
>>> public static void main(String[] args) throws Exception {
>>> Files.writeString(Path.of("./a"), "a".repeat(100000));
>>> Files.writeString(Path.of("./b"), "b".repeat(100000));
>>>
>>> var serverExecutor = Executors.newFixedThreadPool(2);
>>> var baos = new ByteArrayOutputStream();
>>> var server = HttpServer.create(new InetSocketAddress(8841), 0);
>>> server.createContext("/", SimpleFileServer.createFileHandler(Path.of(".").toAbsolutePath()))
>>> .getFilters().add(SimpleFileServer.createOutputFilter(baos, SimpleFileServer.OutputLevel.VERBOSE));
>>> server.setExecutor(serverExecutor);
>>> server.start();
>>>
>>> Thread.sleep(1000);
>>>
>>> var executor = Executors.newFixedThreadPool(2);
>>>
>>> int total = 10000;
>>>
>>> AtomicInteger failures = new AtomicInteger();
>>> for (int i = 0; i < total; i++) {
>>> String file = i % 2 == 0 ? "a" : "b";
>>> executor.submit(() -> {
>>> try {
>>> var client = HttpClient.newHttpClient();
>>> client.send(
>>> HttpRequest.newBuilder(URI.create("http://0.0.0.0:8841/" + file)).build(),
>>> HttpResponse.BodyHandlers.ofString()
>>> );
>>> } catch (Exception e) {
>>> e.printStackTrace(System.out);
>>> failures.getAndIncrement();
>>> }
>>> return null;
>>> });
>>> }
>>>
>>> executor.close();
>>>
>>> int a = 0;
>>> int b = 0;
>>> var s = baos.toString(StandardCharsets.UTF_8).split("\n");
>>> for (var line : s) {
>>> Pattern aPattern = Pattern.compile("Resource requested: (.+)/a");
>>> Pattern bPattern = Pattern.compile("Resource requested: (.+)/b");
>>> if (aPattern.matcher(line).find()) {
>>> a++;
>>> }
>>> else if (bPattern.matcher(line).find()) {
>>> b++;
>>> }
>>> }
>>> System.out.println("Reported a request to /a: " + a);
>>> System.out.println("Reported a request to /b: " + b);
>>> System.out.println("Failures: " + failures);
>>>
>>> server.stop(0);
>>> serverExecutor.close();
>>> }
>>> }
>>>
>>> Despite an equal number of requests being made to /a and /b the output filter will report a randomly diverging amount. This is because there is simply no way to avoid concurrent requests clobbering each-others state while calling setAttribute on an exchange does not actually set an attribute on that exchange.
>>>
>>> On Thu, Dec 5, 2024 at 11:15 PM Ethan McCue <ethan at mccue.dev <mailto:ethan at mccue.dev>> wrote:
>>>> Sorry, meant to send this to the list:
>>>>
>>>> I will add as maybe obvious context that the way the JDK currently implements this is (I think, correct me if I am wrong) a security nightmare. That it might not be obvious (or uniform across an ecosystem of implementations) that exchange.setAttribute("CURRENTLY_AUTHENTICATED_USER", "..."); will not actually be setting an attribute on the exchange, but instead a global thing, is an issue
>>>>
>>>> If this is a deliberate choice in the existing implementation, I am curious to know how it came about.
>>>>
>>>> On Thu, Dec 5, 2024 at 11:13 PM Robert Engels <rengels at ix.netcom.com <mailto:rengels at ix.netcom.com>> wrote:
>>>>> Hi,
>>>>>
>>>>> I read the bug report. I don’t think this is sufficient. This is a specification so in order to have compliant behavior no matter the implementation there cannot be a different set of rules for the reference implementation vs others.
>>>>>
>>>>> So the api should be clarified in a non ambiguous manner and then one or more implementations can be classified as non compliant.
>>>>>
>>>>> Robert
>>>>>
>>>>>> On Dec 5, 2024, at 6:31 AM, Jaikiran Pai <jai.forums2013 at gmail.com <mailto:jai.forums2013 at gmail.com>> wrote:
>>>>>>
>>>>>>
>>>>>> Hello Ethan,
>>>>>>
>>>>>> Thank you for noticing this and bringing this up here. I've raised https://bugs.openjdk.org/browse/JDK-8345577 and we will address this shortly.
>>>>>>
>>>>>> -Jaikiran
>>>>>>
>>>>>> On 05/12/24 3:22 am, Ethan McCue wrote:
>>>>>>> Sorry
>>>>>>>
>>>>>>> Before:
>>>>>>>
>>>>>>> * {@link Filter} modules may store arbitrary objects with {@code HttpExchange}
>>>>>>> * instances as an out-of-band communication mechanism. Other filters
>>>>>>> * or the exchange handler may then access these objects.
>>>>>>>
>>>>>>> Bungled the copy-paste
>>>>>>>
>>>>>>> On Thu, Dec 5, 2024 at 6:49 AM Ethan McCue <ethan at mccue.dev <mailto:ethan at mccue.dev>> wrote:
>>>>>>>> Hi all,
>>>>>>>>
>>>>>>>> This change (https://github.com/openjdk/jdk/commit/40ae4699622cca72830acd146b7b5c4efd5a43ec) makes the Jetty implementation of the SPI be no longer fit the Javadoc.
>>>>>>>>
>>>>>>>> HttpContexts are not per-request while the previous Javadoc implied that the attribute mechanism on exchanges was.
>>>>>>>>
>>>>>>>> Before:
>>>>>>>>
>>>>>>>> * Sets an attribute with the given {@code name} and {@code value} in this exchange's
>>>>>>>> * {@linkplain HttpContext#getAttributes() context attributes}.
>>>>>>>> * or the exchange handler may then access these objects.
>>>>>>>>
>>>>>>>> After:
>>>>>>>>
>>>>>>>> * Sets an attribute with the given {@code name} and {@code value} in this exchange's
>>>>>>>> * {@linkplain HttpContext#getAttributes() context attributes}.
>>>>>>>> *
>>>>>>>> * @apiNote {@link Filter} modules may store arbitrary objects as attributes through
>>>>>>>> * {@code HttpExchange} instances as an out-of-band communication mechanism. Other filters
>>>>>>>> * or the exchange handler may then access these objects.
>>>>>>>>
>>>>>>>> The Jetty implementation, I think rightfully, assumed that this context was per-request and implemented it as so.
>>>>>>>>
>>>>>>>> https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/JettyHttpExchangeDelegate.java#L223
>>>>>>>>
>>>>>>>> As such, I don't think simply a javadoc change as a resolution to these issues is applicable
>>>>>>>>
>>>>>>>> https://bugs.openjdk.org/browse/JDK-8345233
>>>>>>>> https://bugs.openjdk.org/browse/JDK-8235786
>>>>>>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/net-dev/attachments/20241206/f58e8051/attachment.htm>
More information about the net-dev
mailing list