Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ring 2.0 feedback #393

Open
weavejester opened this issue Feb 28, 2020 · 143 comments
Open

Ring 2.0 feedback #393

weavejester opened this issue Feb 28, 2020 · 143 comments

Comments

@weavejester
Copy link
Member

This is an issue for gathering feedback on the Ring 2.0 design.

The code and documentation will be held in the 2.0 branch of the repository.

The first draft specification for Ring 2.0 is now available to read, and accompanying it is an architecture design record that attempts to explain the decision-making behind the design.

New features for Ring 2.0 include:

  • Namespaced request and response maps
  • Request body changed from Java InputStream to protocol
  • Websockets
  • HTTP/2 push promises

Note that Ring does not use semantic versioning. Ring 2.0 will be backward compatible with Ring 1.0, as described in the ADR.

As of writing this design has not yet been implemented in code. The next step is to create an alpha release that implements the draft spec, allowing the community to try the design out. I anticipate Ring 2.0 will remain in alpha for a while, in a similar fashion to Clojure spec, to allow plenty of time for community feedback and testing.

@iku000888
Copy link

🎉

Did you mean :ring.request/body -> :ring.response/body etc in the table?
image

@souenzzo
Copy link

  1. Why not use core.async protocols to async responses?
  2. ring.request keys and ring.push keys has identical specs. Why not reuse ring.request in push maps?
  3. Let's use clojure.core predicates in "types"? I mean, use string? where was String

@weavejester
Copy link
Member Author

Why not use core.async protocols to async responses?

It adds a dependency on core.async.

ring.request keys and ring.push keys has identical specs. Why not reuse ring.request in push maps?

So that we can distinguish between a push map and a request map. A push map also has different mandatory keys to a request map.

Let's use clojure.core predicates in "types"? I mean, use string? where was String

That doesn't work for protocols, and I'd rather be precise and use a type over a predicate. There are some predicates that don't map 1-to-1 with types.

@weavejester
Copy link
Member Author

Did you mean :ring.request/body -> :ring.response/body etc in the table?

Yes, good catch. Let me fix that.

@souenzzo
Copy link

Why not use core.async protocols to async responses?

It adds a dependency on core.async.

So can we consider a new protocol for async API? Then we can easily bridge any async lib into ring protocol.

ring.request keys and ring.push keys has identical specs. Why not reuse ring.request in push maps?

So that we can distinguish between a push map and a request map. A push map also has different mandatory keys to a request map.

Both clojure.spec and plumatic/schema has capabilities to say with keys are required in different contexts. In clojure.spec example:

(s/def ::request (s/keys :req [:ring.request/method]
                         :opt [:ring.request/path]))
(s/def ::push (s/keys :req [:ring.request/path]
                      :opt [:ring.request/method]))

Let's use clojure.core predicates in "types"? I mean, use string? where was String

That doesn't work for protocols, and I'd rather be precise and use a type over a predicate. There are some predicates that don't map 1-to-1 with types.

ring can define ring.response/streamable-response-body? next to the protocol.
Things like Integer don't make sense in some targets like clojurescript/JS, I think that :ring.response/status for example, can be a number?

@weavejester
Copy link
Member Author

So can we consider a new protocol for async API? Then we can easily bridge any async lib into ring protocol.

Can you explain what you mean by "for async API"? Do you mean adding a protocol for non-blocking I/O on the request and response bodies?

Both clojure.spec and plumatic/schema has capabilities to say with keys are required in different contexts.

Sure, but that doesn't help if we want to differentiate between a push promise and a HTTP request dynamically. A push promise also has different semantic meaning to a HTTP request, even though they share fields of the same type.

Things like Integer don't make sense in some targets like clojurescript/JS, I think that :ring.response/status for example, can be a number?

The StreamableResponseBody protocol doesn't make sense for ClojureScript either. The specification is designed to target Clojure, rather than ClojureCLR or ClojureScript.

I have thought about what we'd do to adapt the specification to ClojureScript, and it goes a little deeper than just using predicates rather than type names.

@niquola
Copy link

niquola commented Feb 28, 2020

How about async streaming API - if my handler wants to send chunks?

@niquola
Copy link

niquola commented Feb 28, 2020

We use a lot a context map (with bunch of deps like db connection etc) as a first parameter to handlers - may it be useful for ring?

@dgr
Copy link

dgr commented Feb 28, 2020

The spec obviously spends time describing the differences between the synchronous and asynchronous protocols. One thing it doesn’t seem to mention is the serialization of requests. For instance, can a server have multiple synchronous requests in flight at the same time, or does synchronous imply that requests are serialized, one after another? In particular, if a synchronous handler blocks, is it blocking all requests, or just the request that it’s servicing? Perhaps the omission is on purpose to allow for different server adapter implementations, but the silence is noticeable. If it’s purposeful, perhaps a statement to that effect would be good to include.

@weavejester
Copy link
Member Author

How about async streaming API - if my handler wants to send chunks?

That was added back in Ring 1.6, with the caveat that I/O is blocking. In other words, while your handler is actively writing data to the client it's blocking the thread.

The problem with fully supporting non-blocking I/O in Ring 1 was that the request body was an InputStream, which is a blocking stream. In Ring 2, that's been changed, and in Ring 2.1 the plan is to have NIO protocols that the request body and response body can optionally satisfy.

For Ring 2.0 I thought that sort of performance optimisation was a little out of scope, as it's a fairly niche requirement.

We use a lot a context map (with bunch of deps like db connection etc) as a first parameter to handlers - may it be useful for ring?

In general I think it's generally better to use closures for that:

(fn [context]
  (fn [request]
    ...))

But I'd be interested in hearing about use-cases where closures are not as elegant. That said, I don't anticipate changing the request/response maps into context maps instead.

@weavejester
Copy link
Member Author

For instance, can a server have multiple synchronous requests in flight at the same time, or does synchronous imply that requests are serialized, one after another? In particular, if a synchronous handler blocks, is it blocking all requests, or just the request that it’s servicing?

No, it's just blocking the thread for the current request. It's expected that an adapter that is running a synchronous handler will have a threadpool for handling multiple requests concurrently.

@dgr
Copy link

dgr commented Feb 28, 2020

For instance, can a server have multiple synchronous requests in flight at the same time, or does synchronous imply that requests are serialized, one after another? In particular, if a synchronous handler blocks, is it blocking all requests, or just the request that it’s servicing?

No, it's just blocking the thread for the current request. It's expected that an adapter that is running a synchronous handler will have a threadpool for handling multiple requests concurrently.

OK. My suggestion would be to make that explicit so that nobody has to guess. I assume that the size of the thread pool is really in the domain of the server adapter and shouldn't be part of the Ring spec, but there's a big difference between one (serial) and more than one (non-serial). I'm sure that Ring 1.x behavior already works this way, but given the chance to document it explicitly, I would take the opportunity.

@tonsky
Copy link

tonsky commented Feb 28, 2020

This does make accessing request headers with single values slightly more laborious: (first (get-in request [:headers "content-type"]))

If you are already using get-in, it’s still simple:

(get-in request [:headers "content-type" 0])

@dgr
Copy link

dgr commented Feb 28, 2020

The spec does a great job of considering HTTP/2 issues. Has it been checked against HTTP/3 proposals? I realize that's a moving target and still in flux, but it seems like 2020 is the year that HTTP/3 will start to happen. It's already included in stable versions of Chrome and Firefox, though not the default, per https://en.wikipedia.org/wiki/HTTP/3.

@tonsky
Copy link

tonsky commented Feb 28, 2020

Great spec, all changes very solid and timely!

@dgr
Copy link

dgr commented Feb 28, 2020

BTW, I should also add, great job on Ring 2 and in documenting the rationale behind the decisions. It was a pleasure to read through these documents and I can't wait to use it in anger. Ring is one of the most important APIs in the Clojure ecosystem and you've done an amazing job both with Ring 1 and the evolution to Ring 2, James. Thanks for all your effort over the years.

@alexander-yakushev
Copy link

By contrast, Ring 2 mandates that response header names be lowercase, and the values be vectors:

Is it required for the spec implementor (a web server) to validate this?

An key benefit to being able to identify requests and responses is that we can retain backward compatibility across middleware and utility functions. We can efficiently distinguish between requests that adhere to the 1.x specification, and those that adhere to the 2.x specification, and invoke different behavior depending on the type of input.

I can't help looking at such backward compatibility from the performance perspective. Would this mean that a spec implementation must expose both sets of keys to the middleware/handlers? From a brief glimpse, it feels like this can bring a noticeable overhead from growing the request map, allocating extra objects for the header values (the smallest vector is 240 bytes, btw), and so on.

@SevereOverfl0w
Copy link

I would argue that websockets should not be supported until NIO has landed. WS is one of the strongest use cases for NIO, as a lot of applications have lots of mostly idle connections. Without NIO this is pretty inefficient due to the overheads in managing those connections.

@weavejester
Copy link
Member Author

My suggestion would be to make that explicit so that nobody has to guess. I assume that the size of the thread pool is really in the domain of the server adapter and shouldn't be part of the Ring spec, but there's a big difference between one (serial) and more than one (non-serial).

I'll consider adding a note explaining the terminology.

@weavejester
Copy link
Member Author

Has it been checked against HTTP/3 proposals?

My understanding is that the main difference between HTTP/2 and HTTP/3 is the introduction of QUIC as a transport protocol, which doesn't affect Ring.

@weavejester
Copy link
Member Author

weavejester commented Feb 29, 2020

By contrast, Ring 2 mandates that response header names be lowercase, and the values be vectors:

Is it required for the spec implementor (a web server) to validate this?

No, because by the time it gets to the adapter it's essentially too late. The idea is to make it simpler to write middleware that reads response headers.

The adapter will probably throw an exception if it sees a string where it expects a vector, but it's not the adapter's responsibility to check the header strings are lowercase. Unless it really wants to.

I can't help looking at such backward compatibility from the performance perspective. Would this mean that a spec implementation must expose both sets of keys to the middleware/handlers?

No; an adapter would be started in Ring 1 or Ring 2 mode. Middleware would be able to handle both types, in the same way that middleware can currently handle asynchronous or synchronous handlers.

CPU performance impact is likely to be small; around 6ns added to each request per middleware, so we're talking under 100ns. This may be further reduced by CPU branch prediction, as the same condition branch will be hit each time.

From a brief glimpse, it feels like this can bring a noticeable overhead from growing the request map, allocating extra objects for the header values (the smallest vector is 240 bytes, btw), and so on.

In terms of larger request maps, yes this is true. However, it's far quicker to perform a lookup on a vector than to parse a string, so we'll save some CPU cycles in exchange.

@weavejester
Copy link
Member Author

I would argue that websockets should not be supported until NIO has landed. WS is one of the strongest use cases for NIO, as a lot of applications have lots of mostly idle connections. Without NIO this is pretty inefficient due to the overheads in managing those connections.

Websockets won't be affected by this. I'm delaying NIO specifically for reading request bodies and writing response bodies. A websocket will not take up a thread while waiting for data.

@weavejester
Copy link
Member Author

BTW, I should also add, great job on Ring 2 and in documenting the rationale behind the decisions. It was a pleasure to read through these documents and I can't wait to use it in anger.

Thanks! I'm going to try my best to get a complete alpha out in the next two months.

@weavejester
Copy link
Member Author

If you are already using get-in, it’s still simple:

(get-in request [:headers "content-type" 0])

Yes, that's true.

@DogLooksGood
Copy link

I have a question, according to the ring spec, ring does nothing about how to create the thread that handle the request. And almost every web server library(except aleph) does not depends on async library neither.

So is it possible to use threads in core.async thread pool to handle the request?

@SevereOverfl0w
Copy link

@weavejester you're right, I was not paying enough attention.

I am however a little concerned about the send-message api. Is it sync or async, what happens if you have a slow consumer, what's the backpressure story?

@weavejester
Copy link
Member Author

So is it possible to use threads in core.async thread pool to handle the request?

It's possible, but that's up to the adapter.

@niquola
Copy link

niquola commented Feb 29, 2020

How about async streaming API - if my handler wants to send chunks?

That was added back in Ring 1.6, with the caveat that I/O is blocking. In other words, while your handler is actively writing data to the client it's blocking the thread.

The problem with fully supporting non-blocking I/O in Ring 1 was that the request body was an InputStream, which is a blocking stream. In Ring 2, that's been changed, and in Ring 2.1 the plan is to have NIO protocols that the request body and response body can optionally satisfy.

For Ring 2.0 I thought that sort of performance optimisation was a little out of scope, as it's a fairly niche requirement.

We use a lot a context map (with bunch of deps like db connection etc) as a first parameter to handlers - may it be useful for ring?

In general I think it's generally better to use closures for that:

(fn [context]
  (fn [request]
    ...))

But I'd be interested in hearing about use-cases where closures are not as elegant. That said, I don't anticipate changing the request/response maps into context maps instead.

Closures cons:

  • unaesthetic stack-trace;
  • Implicit state - not so easy to test or change on fly.
  • Providing request as part of context can simplify response middleware.
  • Isn't the first-order function always better than high-order? :)
  • Most handlers and mw should be wrapped this way

The idea of external chain executer instead of wrapping functions as well worth discussion.
What do you think in general about Pedestal interfaces?

@weavejester
Copy link
Member Author

weavejester commented Feb 29, 2020

I am however a little concerned about the send-message api. Is it sync or async, what happens if you have a slow consumer, what's the backpressure story?

Those are excellent points. Ring is designed to try to be adapted to the widest range of libraries, which often means considering the lowest common denominator. In this case the design is a synchronous send, and backpressure and buffering concerns handled by the adapter.

However, your comment has made me reconsider the design. Though not every existing Java and Clojure websocket implementation supports asynchronous sending, the majority of them do, and the key advantage of an asynchronous send is that developers can implement bespoke backpressure handling.

I'm considering adding an optional protocol like:

(defprotocol AsyncSocket
  (async-send [socket message callback]))

Or possibly just extending the current protocol. This will require a little hammock time, so feel free to suggest designs or raise further concerns in the meantime.

@weavejester
Copy link
Member Author

Closures cons:

  • unaesthetic stack-trace;

Can you give an example?

  • Implicit state - not so easy to test or change on fly.

I'm not sure what you mean by this. Why does adding another lexical scope make things more implicit or harder to test?

  • Providing request as part of context can simplify response middleware.

How so?

  • Isn't the first-order function always better than high-order? :)

Why?

  • Most handlers and mw should be wrapped this way

I can see handlers often requiring context, such as a database connection, but I've rarely needed that for middleware outside of authorization middleware. Could you give some examples of other middleware that would require a context?

@celiocidral
Copy link

Silly question: what's the motivation behind using a protocol for defining websocket listeners?

@celiocidral
Copy link

Also, are we planning to support ping/pong frame handlers?

@weavejester
Copy link
Member Author

Silly question: what's the motivation behind using a protocol for defining websocket listeners?

It allows for more flexibility than, say, a map of functions. For example, you could respond with a core.async channel.

Also, are we planning to support ping/pong frame handlers?

Not initially; I wanted to keep the core interface minimal to provide the greatest width of base API support.

@celiocidral
Copy link

celiocidral commented Nov 1, 2021

Thanks for the quick reply!

Regarding ping/pong support, that would be a huge win for people who implement the graphql-ws protocol need some kind of keep-alive mechanism on the backend. I believe most, if not all, websocket clients and servers are expected to support ping/pong control frames given that they are part of RFC 6455. Is there any chance to consider adding them to this spec?

Edit: The graphql-ws protocol doesn't rely on ping/pong control frames. Instead it defines specific ping/pong messages for application-specific purposes typically on the frontend. Anyway, it's not uncommon to have some kind of keep-alive loop at the backend that rely on ping/pong control frames, as is our case here at Beacon.

@weavejester
Copy link
Member Author

I believe most, if not all, websocket clients and servers are expected to support ping/pong control frames given that they are part of RFC 6455.

So to be clear, these frames would be supported by the backend, insofar as a ping from the client would automatically result in a pong from the server. They just wouldn't be accessible to the developer through the Ring protocol, at least at initial release.

@sunng87
Copy link

sunng87 commented Nov 1, 2021

According to https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2, ping frames are allowed to carry application data. It would be nice to have access to ping and its body.

@weavejester
Copy link
Member Author

According to https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2, ping frames are allowed to carry application data. It would be nice to have access to ping and its body.

Sure; nice to have, but not essential. I'm not saying we shouldn't have this, just that it shouldn't be a day 1 feature. It's not part of a minimal viable interface for websockets.

@niwinz
Copy link

niwinz commented Dec 21, 2021

hello @weavejester

What is the status of this? Can you rebase the 2.0 branch with master? We have plans to integrate the ring2 support in our adapter for testing purposes and would be awesome knowing the status of this and having it rebased on master or at least on top of latest stable release.

Thanks

@weavejester
Copy link
Member Author

Hi @niwinz. I'm afraid other obligations on my time have delayed Ring 2.0. However, I can see about squeezing some time in to merge master into the 2.0 branch, and of course help would always be appreciated.

@niwinz
Copy link

niwinz commented Dec 23, 2021

I find the time I will try to prepare the merge for you. :D

@weavejester
Copy link
Member Author

@niwinz master has now been merged into 2.0.

@niwinz
Copy link

niwinz commented Feb 3, 2022

thanks a lot, unfortunately as you can see I didn't find time to do it myself :(

@jeroenvandijk
Copy link

jeroenvandijk commented Feb 15, 2022

@weavejester Is it possible to push a new alpha release to clojars? I would like to do some testing and there are quite some significant changes since the previous alpha release (details).

Thank you in advance.

(FYI, I tried to create a fork with a deps.edn file, but this conflicts with other ring-core versions)

@onetom
Copy link

onetom commented Mar 25, 2022

Why the keys in :ring.request/headers and/or :ring.response/headers are strings and NOT - potentially fully-qualified - keywords or symbols?

That could further simplify working with headers.

For example:

  1. you can just thread a request (-> req :headers :location).
  2. you can minimize the amount of code change needed, when you move some parameter between the :body and the :query-string or :headers.
  3. you can use editor tooling to auto-complete keywords and symbols, but not strings

The only reason I can think of for keeping header keys as strings is some kind of runtime performance gain, since it would require a (comp keyword str/lower-case) conversion for :ring.request/headers, instead of just str/lower-case and a (comp str/lower-case str symbol) somewhere, before handing it over to the adapter. (the symbol step is there to keep the optional namespace part of the header key, but don't prefix the resulting string with a :.)

Would the saving on omitting such extra keyword conversion step worth the trade-off for the degraded developer experience?

As Rich points out in https://clojure.org/about/spec#_global_namespaced_names_are_more_important

Clojure supports namespaced keywords and symbols. Note here we are just talking about namespace-qualified names, not Clojure namespace objects. These are tragically underutilized and convey important benefits because they can always co-reside in dictionaries/dbs/maps/sets without conflict.

In the case of :ring.*/headers, we are not even using keywords, let alone fully qualified ones...

Similarly, we are using the namespace information in https://github.com/ring-clojure/ring-codec/blob/master/src/ring/util/codec.clj#L104 too, which affects form data and query params, when using ring.mock.request.

All these little frictions add up to frustration and gets in the way of delivering on Clojure's core value proposition of working with fully-qualified names. Such frustrations amounted my colleagues proposing NOT to use fully-qualified keys, because the various tools and libraries are not very compatible with them.

Encouraging their usage, by making certain choices at such low layers as the Ring spec, might have an amplified effect on the eco-system and at least in the Clojure parts of our code-bases we wouldn't need to fight against the non-qualified world of computing. :)

Alternatively, we can agree on some kind of an extension to Ring 2.0, which lifts the most common header fields up, into the top of the request map, under fully qualified keys, which then could be even validated/generated with clojure.spec. Eg:

{:ring.request.header/accept {:internet.media/type :application/json ...}
 :ring.request.header/content-type {:internet.media/type :application/json ...}}
{:ring.response.header/content-type {:internet.media/type :application/json ...}}

That way we can decouple the problem of how do we parse the various, more complicated header values.
It would also help people to understand the distinction/relation between the request/content-type, request/accept and response/content-type, which I found to be a constant source of confusion for people for decades now.

@weavejester
Copy link
Member Author

Why the keys in :ring.request/headers and/or :ring.response/headers are strings and NOT - potentially fully-qualified - keywords or symbols?

Because they come from the client, rather than something the developer has control over. The developer might not necessarily want to intern every header, and not every valid header name is a valid keyword or symbol name.

@KingMob
Copy link
Contributor

KingMob commented Jul 2, 2023

Should ring2 include support for things like HTTP Trailer?
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer

I've wondered about exactly this lately while adding HTTP/2 to Aleph. If you block until you are completely done reading, it's not such an issue, but it doesn't play nicely with streaming scenarios where you want to start once the headers are done, and process body data as it streams in.

I haven't put too much thought into it yet, but adding a trailers deferred/promise may work.

@KingMob
Copy link
Contributor

KingMob commented Jul 2, 2023

From SPEC-2.md:

:ring.request/path
The absolute path of the URI in the HTTP request. Must start with a /.

This is not technically true for OPTIONS and CONNECT methods: https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.4.1

The new Ring ADR talks about the path in relation to OPTIONS and CONNECT, and it's correct that CONNECT will have no :path, but it's a little unclear on OPTIONS. It seems to be saying (to me), that OPTIONS * implies the path is empty. But in RFC 9113, it says

these MUST include a ":path" pseudo-header field with a value of '*'

Also, I think more people will read the SPEC than the ADR, so maybe it's clearer to mention there that :ring.request/path will not include the query-string or the ?, since that conflicts with the definition of the :path pseudo-header.

@KingMob
Copy link
Contributor

KingMob commented Jul 2, 2023

:ring.request/protocol
The protocol the request was made with, e.g. "HTTP/1.1".

I'm not sure how many users will actually use this key, but I think we should clarify the version number. Colloquially, since HTTP/2 hasn't had a minor version number, and HTTP/2 frames carry no version info, most people refer to it just "HTTP/2". But the RFC says that implicitly, the version number should be "2.0".

I think we should specify it'll be "HTTP/2.0" so there's no confusion.

@weavejester
Copy link
Member Author

The new Ring ADR talks about the path in relation to OPTIONS and CONNECT, and it's correct that CONNECT will have no :path, but it's a little unclear on OPTIONS. It seems to be saying (to me), that OPTIONS * implies the path is empty. But in RFC 9113, it says

these MUST include a ":path" pseudo-header field with a value of '*'

Right, but the path will always be * in the raw request for OPTIONS, and therefore can be omitted from the Ring request map without losing information.

@KingMob
Copy link
Contributor

KingMob commented Jul 3, 2023

Sure, that works, but I think the Ring SPEC should be more precise on these points. Right now, it seems like the ADR is more detailed than the SPEC itself, but I'm pretty sure more people will refer to the SPEC once published.

Maybe something like:

:ring.request/path
The absolute path of the URI in the HTTP request. If present, must start with a /. Must not be present for CONNECT requests. May be omitted for OPTIONS requests; if so, a path of * is implied.

@weavejester
Copy link
Member Author

That's a reasonable point. It might also be worth allowing a path of * for greater parity with the raw request, even if that does create an extra edge case to deal with (i.e. we can no longer assume the path is a path).

@KingMob
Copy link
Contributor

KingMob commented Jul 4, 2023

It's true we couldn't consider the path as always a path in that case. Hopefully, anyone responding to OPTIONS knows what they're doing, though. Luckily, it wouldn't affect the bread-and-butter GET/POST/etc requests either way. Most users will probably never notice.

@KingMob
Copy link
Contributor

KingMob commented Dec 20, 2023

@weavejester Think we could maybe include WebTransports in the Ring 2 spec?

WebSockets have the problem that all the servers already have their own ad hoc implementation; specifying how to use WebTransports could avoid the same issue, since I don't think any Clojure server's implemented them yet.

@weavejester
Copy link
Member Author

The Ring 2.0 specification was a redesign of the current request/response maps, along with support for new features such as WebSockets. This proved to be too ambitious, so instead new features will be added piecemeal. In Ring 1.11.0, for example, WebSocket support is going to be added.

In 1.12.0 I plan on adding support for HTTP/2 to the Jetty adapter, along with some other changes, so it'll be a while before HTTP/3, and by extension WebTransports, will be supported. However, we can certainly start thinking about the design of them, and to collaborate with third-party adapters to present a more common approach from day 1.

I've also been working with several third-party adapters, both to get feedback on the WebSocket API, and to help with the implementation. After Ring 1.11.0 is released, it shouldn't be long before rja9, httpkit, and the Luminus undertow adapter also support Ring's WebSocket API, which hopefully more to follow.

@LouDnl
Copy link

LouDnl commented Mar 21, 2024

The Ring 2.0 specification was a redesign of the current request/response maps, along with support for new features such as WebSockets. This proved to be too ambitious, so instead new features will be added piecemeal. In Ring 1.11.0, for example, WebSocket support is going to be added.

In 1.12.0 I plan on adding support for HTTP/2 to the Jetty adapter, along with some other changes, so it'll be a while before HTTP/3, and by extension WebTransports, will be supported. However, we can certainly start thinking about the design of them, and to collaborate with third-party adapters to present a more common approach from day 1.

I've also been working with several third-party adapters, both to get feedback on the WebSocket API, and to help with the implementation. After Ring 1.11.0 is released, it shouldn't be long before rja9, httpkit, and the Luminus undertow adapter also support Ring's WebSocket API, which hopefully more to follow.

Did HTTP/2 make it into 1.12.0?

@weavejester
Copy link
Member Author

Did HTTP/2 make it into 1.12.0?

No, I decided that it was better to release sooner rather than delay 1.12. You can always check the changelog to see what's in any particular Ring release.

@LouDnl
Copy link

LouDnl commented Mar 21, 2024

Did HTTP/2 make it into 1.12.0?

No, I decided that it was better to release sooner rather than delay 1.12. You can always check the changelog to see what's in any particular Ring release.

I did check indeed, was just wondering, Thanks for the update!

@KingMob
Copy link
Contributor

KingMob commented Mar 22, 2024

@LouDnl A lot of HTTP/2 is invisible or backwards-compatible at the Ring level. For 97% of use cases, the Ring map would look practically the same.

Are there HTTP/2 or 3 features you want exposed in Ring?

@LouDnl
Copy link

LouDnl commented Mar 23, 2024

@KingMob no exact features, I'm just reading up on and experimenting with sse, websockets etc.
I read that http1.1 only supports a max of 2~6 connections over multiple tabs and that http2 supports more. But then again, my knowledge about this is still too limited to fully understand.
I see now that rfc2616 which it was in is obsolete and that rfc9110 that replaced it does not mention this limitation.

@KingMob
Copy link
Contributor

KingMob commented Mar 23, 2024

@LouDnl 2-6 connections wasn't a limitation of HTTP/1, but a limitation imposed by browsers. HTTP/1 itself is technically only limited by the number of TCP ports, so your computer couldn't have more than 2^16=65536 connections to one server. More realistically, limits of that era were things like per-conn overhead, link bandwidth saturation, etc.

With HTTP/2 streams, you can multiplex a single TCP connection up to 2^31 streams. You have your work cut out for you to actually use up all of those.

If you want to know more about HTTP/2, check out RFC 9113, not RFC 9110. 9110 is more about the HTTP protocol semantics (e.g., what does GET mean, what's 204 mean? etc), which hasn't changed as much, while 9113 is more about the actual H2 framing, streams, connections, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests