dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.21k stars 9.95k forks source link

HTTP/2: Server Push support #4249

Closed Saphirim closed 3 years ago

Saphirim commented 5 years ago

Support the HTTP/2 Server Push feature with the now previewed and for .Net Core 2.2 most likely GA HTTP/2 implementation.

HTTP/2 Server Push allows an HTTP/2-compliant server to send resources to a HTTP/2-compliant client before the client requests them. It is, for the most part, a performance technique that can be helpful in loading resources pre-emptively.

Tratcher commented 5 years ago

No, server push will not be available in any AspNetCore servers for 2.2. We're still trying to gauge interest for 3.0.

Saphirim commented 5 years ago

Interest here 😊 thx for your fast reply.

tpeczek commented 5 years ago

Same here.

CShepartd commented 5 years ago

If not 2.2 then 3.0 will be nice

asbjornu commented 5 years ago

🙋🏼‍♂️ I would love to have the ability to push resources over an established HTTP/2 connection with ASP.NET.

davidfowl commented 5 years ago

It would help if you can detail how you think you would take advantage of this feature in real applications. Code samples help as well.

asbjornu commented 5 years ago

I would like to use HTTP/2 Server Push to push related resources to a client; typically when a “collection” resource has been requested, each “item” resource in the collection could be pushed alongside the “collection” resource so they are available once the client starts iterating over the collection to fetch the individual items.

The alternative (and imho. sub-par) approach is to compound (“embed”, “transclude”, “include”, “expand”, etc.) all items within the collection resource, making caching of the collection resource hard and of the item resources impossible.

The relationship between the resources can also be seen as “parent” and “child”, “root” and “subresource”, etc., but the requirements are similar regardless of the relationship between the resources. Server push could provide helpful in all cases.

davidfowl commented 5 years ago

How does that concretely change what you’re doing today? What are you doing today? What big downsides are you seeing and what would you expect the big upside to be.

I’m looking for something very concrete and not abstract, that’ll help us think about how to prioritize and even implement the feature.

asbjornu commented 5 years ago

@davidfowl, sure. Examples of how we (as an industry) deal with the problem of transclusion over HTTP/1.1 today can be found in GraphQL and OData. In GraphQL, transclusion is expressed as sub-selection:

POST /query HTTP/1.1
Host: example.com
Content-Type: application/graphql

{
  hero {
    friends {
      name
      height
    }
  }
}
HTTP/1.1 200 OK 
Content-Type: application/json

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker",
          "height": 1.72
        },
        {
          "name": "Han Solo",
          "height": 1.85
        },
        {
          "name": "Leia Organa",
          "height": 1.54
        }
      ]
    }
  }
}

In OData, transclusion is expressed with the $expand keyword:

GET /heroes/r2-d2?$expand=friends($select=name,height) HTTP/1.1
Host: example.com
HTTP/1.1 200 OK 
Content-Type: application/json

{
  "hero": {
    "name": "R2-D2",
    "friends": [
      {
        "name": "Luke Skywalker",
        "height": 1.72
      },
      {
        "name": "Han Solo",
        "height": 1.85
      },
      {
        "name": "Leia Organa",
        "height": 1.54
      }
    ]
  }
}

Each friend in the above responses are most likely HTTP resources themselves. There's no information provided that allows REST's layered constraint to be used to allow intermediary caching, resource-level RBAC or other resource-specific optimizations, since every "sub-resource" is compounded into one large response.

With HTTP/2, we can use Server Push to deliver each hero as its own resource instead. Hypothetical example based on @evert's Prefer-Push draft:

GET /heroes/r2-d2 HTTP/2
Prefer-Push: friends
Host: example.com
HTTP/2 200 OK
Content-Type: application/json

{
  "hero": {
    "name": "R2-D2",
    "friends": [
      {
        "id": "https://example.com/heroes/luke-skywalker"
      },
      {
        "id": "https://example.com/heroes/han-solo"
      },
      {
        "id": "https://example.com/heroes/leia-organa"
      }
    ]
  }
}

Following this response, three PUSH_PROMISE frames will be sent by the server; one for each friend:

:path /heroes/luke-skywalker
:authority example.com
:path /heroes/han-solo
:authority example.com
:path /heroes/leia-organa
:authority example.com

Then three HEADER and DATA frames are sent by the server containing the headers and contents of the three hero resources:

Content-Type: application/json

{
  "hero": {
    "name": "Luke Skywalker",
    "height": 1.72,
    "friends": [
      {
        "id": "https://example.com/heroes/r2-d2"
      },
      {
        "id": "https://example.com/heroes/han-solo"
      },
      {
        "id": "https://example.com/heroes/leia-organa"
      }
    ]    
  }
}
Content-Type: application/json

{
  "hero": {
    "name": "Han Solo",
    "height": 1.85,
    "friends": [
      {
        "id": "https://example.com/heroes/r2-d2"
      },
      {
        "id": "https://example.com/heroes/luke-skywalker"
      },
      {
        "id": "https://example.com/heroes/leia-organa"
      }
    ]
  }
}
Content-Type: application/json

{
  "hero": {
    "name": "Leia Organa",
    "height": 1.54,
    "friends": [
      {
        "id": "https://example.com/heroes/r2-d2"
      },
      {
        "id": "https://example.com/heroes/luke-skywalker"
      },
      {
        "id": "https://example.com/heroes/han-solo"
      }
    ]
  }
}

Now that each hero has been pushed with its own URI, intermediaries can cache them, authorize them, individually on a per-resource basis. This also has two added bonuses:

  1. The initial R2-D2 response will be more cacheable by being less tailored to the initial request.
  2. The recursive and stack overflow-inducing nature of the friends list is mitigate by not compounding every friend inside every friend, but instead referring to them by their URI.

How we should signal pushing of resources from the client to the server is still up for discussion. preload and prefetch are both viable options that are being investigated. Regardless of the initiation mechanism, a way to do HTTP/2 Server Push in an ASP.NET Core application is needed.

serialseb commented 5 years ago

Same scenario as above, we currently preload those as soon as we get the headers, before we parse the body, so we have headers -> links -> download body (and download links in the background), and finally reassemble once parsing has been done for the request entity body. We lose quite a bit of time in that model, and we have to maintain client code and specific infrastructure for it, which is a pain. People don't like pain. It's a good model. We can't experiment further with push as a replacement because of the lack of implementation.

Daniel15 commented 4 years ago

For what it's worth, Nginx supports HTTP/2 push (see https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/), so if you're using Nginx as a reverse proxy in front of your ASP.NET service, you just have to enable the http2_push_preload option and send the relevant preload Link headers and Nginx will convert that into a HTTP/2 push for you.

CShepartd commented 4 years ago

@Daniel15 This is bad replace for real HTTP/2 Server Push support cuz of:

NGINX intercepts this header and commences a server push of /style.css. The path in the Link header must be absolute – relative paths like ./style.css are not supported

Adding additional configs in Nginx for cover this isn't right answer

pedroreys commented 4 years ago

+1 for adding Server Push support. That way we could implement something like Vulcain directly instead of having to use their Gateway Server for the Server Push support.

naveedahmed1 commented 4 years ago

Does. Net core 3.1 supports HTTP/2 Server push?

davidfowl commented 4 years ago

No it does not

naveedahmed1 commented 4 years ago

Should we expect its support in .Net Core any time soon?

davidfowl commented 4 years ago

No, we're not working on this feature. The cost is high and it isn't a priority for the current release.

naveedahmed1 commented 4 years ago

How about .Net 5, any plans to consider it for next major release?

davidfowl commented 4 years ago

.NET 5 is the currrent release, so do you mean .NET 6? Sure nothing has been planned for that as yet.

naveedahmed1 commented 4 years ago

Thanks @davidfowl, I thought .Net Core 3.x is considered as current release.

HTTP/2 server push can have major effect on the performance of web applications since it can improve page load time by pushing the required resources in a single request. And now most of the browsers support this feature, don't you think its support should be added to one of the most popular web development platform?

Ref: https://developers.google.com/web/fundamentals/performance/http2#server_push

Tratcher commented 4 years ago

HTTP/2 server push can have major effect on the performance of web applications

These benefits are largely theoretical and come with a lot of issues of their own (e.g. cache busting). The various standards groups are still trying to make it work in practice.

davidfowl commented 4 years ago

Sorry current release as in the one we are currently working on. We already have planned and have started working on .NET 5 for a few months now.

ygoe commented 4 years ago

I'd like to see push support to be able to send messages to a REST API client from the server. I've just started playing with HTTP2 but failed immediately and can't recover. So I went on and checked push support. Now that I see it's not there and not coming in the foreseeable future, I can give up on HTTP2 altogether. This means I need to find another solution to send events through a REST API (or something similar and lightweight).

PS. It's a local connection, so nothing over a network. Maybe classic .NET remoting would work here. 😆

Tratcher commented 4 years ago

@ygoe HTTP/2 PUSH is not an event API, the client still has to request the resource from the HTTP stack. You should look at WebSockets, SignalR, etc..

evert commented 4 years ago

To pile on on to @tracychms , HTTP/2 Push is really a mechanism to 'warm the cache' and send responses to GET requests that the client will need to make soon anyway. It removes a potential round-trip. Still useful for speeding up APIs

ygoe commented 4 years ago

OK, must have misunderstood this. But hasn't there been such a message send thing somewhere around HTTP a few years ago? Or was that WebSocket only?

evert commented 4 years ago

Websocket enables server-initiated messaging to a browser, and it piggybacks on HTTP/1.1 to initiate the connection. Websocket over HTTP/2 will use HTTP/2 frames.

I think there was a lot of misunderstanding over what HTTP/2 Push meant. If it's helpful I wrote something about the use-case of HTTP/2 push in the context of APIs: https://evertpot.com/h2-parallelism/

ghost commented 3 years ago

Thanks for contacting us. We're moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

JamesNK commented 3 years ago

For reference, System.Web API for this - https://docs.microsoft.com/en-us/dotnet/api/system.web.httpresponse.pushpromise?view=netframework-4.8

davidfowl commented 3 years ago

Further reference, this is being removed from chrome https://groups.google.com/a/chromium.org/g/blink-dev/c/K3rYLvmQUBY/m/vOWBKZGoAQAJ

Almost five and a half years after the publication of the HTTP/2 RFC, server push is still extremely rarely used. Over the past 28 days, 99.95% of HTTP/2 connections created by Chrome never received a pushed stream (Net.SpdyStreamsPushedPerSession), and 99.97% of connections never received a pushed stream that got matched with a request (Net.SpdyStreamsPushedAndClaimedPerSession). These numbers are exactly the same as in June 2019. In June 2018, 99.96% of HTTP/2 connections never received a pushed stream. These numbers indicate the lack of active efforts by server operators to increase push usage. On top of this, less than 40% of received pushes are used, down from 63.51% two years ago. The rest are invalid, never get matched to a request, or already in cache.

asbjornu commented 3 years ago

Thanks for sharing, @davidfowl. First I have to say that the Chrome developers are overly impatient. For complex enough concepts and technologies, 5 years is nothing. Two examples:

  1. Fielding wrote his REST thesis 20 years ago and most developers still have no idea what it's about.
  2. It took the concept of lambda expressions 71 years from its inception in 1936 before it was popularized and made accessible to regular (dark matter) developers in C# 3.0 in 2007.

Secondly, I believe server push to be much more useful with fine-grained control in API use cases than it has been in browsers. Browser support is thus not very relevant, at least not for the use cases I've envisioned.

tpeczek commented 3 years ago

Hi @davidfowl,

It looks the intent is to pivot toward 103 Early Hints as Chrome has recently started an experiment to evaluate their effectiveness.

It looks like, similar to Server Push, this is not something which can be added on top of ASP.NET Core in .NET 5, unless I'm missing an extensibility point which allows emitting informational responses for HTTP/2.

Is this something which could be tracked/considered for .NET 6 (depending on that experiment results)?

Tratcher commented 3 years ago

Yes 103 Early hints would require new API, please open a new issue for that. It would be pretty strait forward in Kestrel. This new feature isn't HTTP/2 specific, it should work with any version.

Note 1xx responses are so uncommon that to enable this on IIS/Http.Sys would require work in Http.Sys at the OS level. Only 100 Continue and 101 Switching Protocols are supported today, and those are both managed closely by Http.Sys, ASP.NET and IIS have minimal control over them.

ygoe commented 3 years ago

Two examples

Add to that IPv6. 25 years or so old and Vodafone still has no clue what that is or that it exists while other providers have almost switched off IPv4 a few years ago.

Glancing over the Google comment, it seems like Push is not very helpful but overly complex. I also had no intentions to make use of Push.

tpeczek commented 3 years ago

@Tratcher I've created #27851

shirhatti commented 3 years ago

Reposting @davidfowl's earlier comment from Chrome's intent to remove HTTP/2 server push

Almost five and a half years after the publication of the HTTP/2 RFC, server push is still extremely rarely used. Over the past 28 days, 99.95% of HTTP/2 connections created by Chrome never received a pushed stream (Net.SpdyStreamsPushedPerSession), and 99.97% of connections never received a pushed stream that got matched with a request (Net.SpdyStreamsPushedAndClaimedPerSession). These numbers are exactly the same as in June 2019. In June 2018, 99.96% of HTTP/2 connections never received a pushed stream. These numbers indicate the lack of active efforts by server operators to increase push usage. On top of this, less than 40% of received pushes are used, down from 63.51% two years ago. The rest are invalid, never get matched to a request, or already in cache.

Given that HTTP/2 server push was intended as a performance optimization and studiessup>[\[1\]](https://github.com/httpwg/wg-materials/blob/gh-pages/ietf102/chrome_push.pdf)[\[2\]](https://github.com/httpwg/wg-materials/blob/gh-pages/ietf102/akamai-server-push.pdf)[\[3\]](https://medium.com/@ananner/http-2-server-push-performance-a-further-akamai-case-study-7a17573a3317)</sup have shown little to no performance improvements, the ASP.NET Core team has decided not do this.

[1]: https://github.com/httpwg/wg-materials/blob/gh-pages/ietf102/chrome_push.pdf [2]: https://github.com/httpwg/wg-materials/blob/gh-pages/ietf102/akamai-server-push.pdf [3]: https://medium.com/@ananner/http-2-server-push-performance-a-further-akamai-case-study-7a17573a3317