Change negotiate to POST (#1122)
This commit is contained in:
parent
046553cfe4
commit
93cbf4dbef
|
|
@ -9,4 +9,15 @@ export function eachTransport(action: (transport: TransportType) => void) {
|
|||
TransportType.ServerSentEvents,
|
||||
TransportType.LongPolling ];
|
||||
transportTypes.forEach(t => action(t));
|
||||
};
|
||||
};
|
||||
|
||||
export function eachEndpointUrl(action: (givenUrl: string, expectedUrl: string) => void) {
|
||||
let urls = [
|
||||
[ "http://tempuri.org/endpoint/?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data" ],
|
||||
[ "http://tempuri.org/endpoint?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data" ],
|
||||
[ "http://tempuri.org/endpoint", "http://tempuri.org/endpoint/negotiate" ],
|
||||
[ "http://tempuri.org/endpoint/", "http://tempuri.org/endpoint/negotiate" ]
|
||||
];
|
||||
|
||||
urls.forEach(t => action(t[0], t[1]));
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import { HttpConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/HttpCo
|
|||
import { IHttpConnectionOptions } from "../Microsoft.AspNetCore.SignalR.Client.TS/IHttpConnectionOptions"
|
||||
import { DataReceived, TransportClosed } from "../Microsoft.AspNetCore.SignalR.Client.TS/Common"
|
||||
import { ITransport, TransportType, TransferMode } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports"
|
||||
import { eachTransport } from "./Common";
|
||||
import { eachTransport, eachEndpointUrl } from "./Common";
|
||||
|
||||
describe("Connection", () => {
|
||||
it("cannot be created with relative url if document object is not present", () => {
|
||||
|
|
@ -24,7 +24,7 @@ describe("Connection", () => {
|
|||
it("starting connection fails if getting id fails", async (done) => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.reject("error");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
|
|
@ -50,7 +50,7 @@ describe("Connection", () => {
|
|||
it("cannot start a running connection", async (done) => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
connection.start()
|
||||
.then(() => {
|
||||
fail();
|
||||
|
|
@ -84,7 +84,7 @@ describe("Connection", () => {
|
|||
it("cannot start a stopped connection", async (done) => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.reject("error");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
|
|
@ -118,7 +118,7 @@ describe("Connection", () => {
|
|||
it("can stop a starting connection", async (done) => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
connection.stop();
|
||||
return Promise.resolve("{}");
|
||||
},
|
||||
|
|
@ -165,7 +165,7 @@ describe("Connection", () => {
|
|||
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\" }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
|
|
@ -191,6 +191,39 @@ describe("Connection", () => {
|
|||
done();
|
||||
});
|
||||
|
||||
eachEndpointUrl((givenUrl: string, expectedUrl: string) => {
|
||||
it("negotiate request puts 'negotiate' at the end of the path", async done => {
|
||||
let negotiateUrl: string;
|
||||
let connection: HttpConnection;
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
negotiateUrl = url;
|
||||
connection.stop();
|
||||
return Promise.resolve("{}");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
connection.stop();
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
logging: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
connection = new HttpConnection(givenUrl, options);
|
||||
|
||||
try {
|
||||
await connection.start();
|
||||
done();
|
||||
} catch (e) {
|
||||
fail();
|
||||
done();
|
||||
}
|
||||
|
||||
expect(negotiateUrl).toBe(expectedUrl);
|
||||
});
|
||||
});
|
||||
|
||||
eachTransport((requestedTransport: TransportType) => {
|
||||
// OPTIONS is not sent when WebSockets transport is explicitly requested
|
||||
if (requestedTransport === TransportType.WebSockets) {
|
||||
|
|
@ -199,7 +232,7 @@ describe("Connection", () => {
|
|||
it(`cannot be started if requested ${TransportType[requestedTransport]} transport not available on server`, async done => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
|
|
@ -226,7 +259,7 @@ describe("Connection", () => {
|
|||
it("cannot be started if no transport available on server and no transport requested", async done => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
|
|
@ -248,10 +281,10 @@ describe("Connection", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('does not send OPTIONS request if WebSockets transport requested explicitly', async done => {
|
||||
it('does not send negotiate request if WebSockets transport requested explicitly', async done => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.reject("Should not be called");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
|
|
@ -270,7 +303,7 @@ describe("Connection", () => {
|
|||
}
|
||||
catch (e) {
|
||||
// WebSocket is created when the transport is connecting which happens after
|
||||
// OPTIONS request would be sent. No better/easier way to test this.
|
||||
// negotiate request would be sent. No better/easier way to test this.
|
||||
expect(e.message).toBe("WebSocket is not defined");
|
||||
done();
|
||||
}
|
||||
|
|
@ -295,7 +328,7 @@ describe("Connection", () => {
|
|||
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { HttpError } from "./HttpError"
|
|||
|
||||
export interface IHttpClient {
|
||||
get(url: string, headers?: Map<string, string>): Promise<string>;
|
||||
options(url: string, headers?: Map<string, string>): Promise<string>;
|
||||
post(url: string, content: string, headers?: Map<string, string>): Promise<string>;
|
||||
}
|
||||
|
||||
|
|
@ -14,10 +13,6 @@ export class HttpClient implements IHttpClient {
|
|||
return this.xhr("GET", url, headers);
|
||||
}
|
||||
|
||||
options(url: string, headers?: Map<string, string>): Promise<string> {
|
||||
return this.xhr("OPTIONS", url, headers);
|
||||
}
|
||||
|
||||
post(url: string, content: string, headers?: Map<string, string>): Promise<string> {
|
||||
return this.xhr("POST", url, headers, content);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export class HttpConnection implements IConnection {
|
|||
this.transport = this.createTransport(this.options.transport, [TransportType[TransportType.WebSockets]]);
|
||||
}
|
||||
else {
|
||||
let negotiatePayload = await this.httpClient.options(this.url);
|
||||
let negotiatePayload = await this.httpClient.post(this.resolveNegotiateUrl(this.url), "");
|
||||
let negotiateResponse: INegotiateResponse = JSON.parse(negotiatePayload);
|
||||
this.connectionId = negotiateResponse.connectionId;
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ export class HttpConnection implements IConnection {
|
|||
}
|
||||
|
||||
if (this.connectionId) {
|
||||
this.url += (this.url.indexOf("?") == -1 ? "?" : "&") + `id=${this.connectionId}`;
|
||||
this.url += (this.url.indexOf("?") === -1 ? "?" : "&") + `id=${this.connectionId}`;
|
||||
this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports);
|
||||
}
|
||||
}
|
||||
|
|
@ -189,6 +189,17 @@ export class HttpConnection implements IConnection {
|
|||
return normalizedUrl;
|
||||
}
|
||||
|
||||
private resolveNegotiateUrl(url: string): string {
|
||||
let index = url.indexOf("?");
|
||||
let negotiateUrl = this.url.substring(0, index === -1 ? url.length : index);
|
||||
if (negotiateUrl[negotiateUrl.length - 1] !== "/") {
|
||||
negotiateUrl += "/";
|
||||
}
|
||||
negotiateUrl += "negotiate";
|
||||
negotiateUrl += index === -1 ? "" : url.substring(index);
|
||||
return negotiateUrl;
|
||||
}
|
||||
|
||||
onreceive: DataReceived;
|
||||
onclose: ConnectionClosed;
|
||||
}
|
||||
|
|
@ -12,13 +12,13 @@ A transport is required to have the following attributes:
|
|||
|
||||
The only transport which fully implements the duplex requirement is WebSockets, the others are "half-transports" which implement one end of the duplex connection. They are used in combination to achieve a duplex connection.
|
||||
|
||||
Throughout this document, the term `[endpoint-base]` is used to refer to the route assigned to a particular end point. The term `[connection-id]` is used to refer to the connection ID provided by the `OPTIONS [endpoint-base]` request.
|
||||
Throughout this document, the term `[endpoint-base]` is used to refer to the route assigned to a particular end point. The term `[connection-id]` is used to refer to the connection ID provided by the `POST [endpoint-base]/negotiate` request.
|
||||
|
||||
**NOTE on errors:** In all error cases, by default, the detailed exception message is **never** provided; a short description string may be provided. However, an application developer may elect to allow detailed exception messages to be emitted, which should only be used in the `Development` environment. Unexpected errors are communicated by HTTP `500 Server Error` status codes or WebSockets non-`1000 Normal Closure` close frames; in these cases the connection should be considered to be terminated.
|
||||
|
||||
## `OPTIONS [endpoint-base]` request
|
||||
## `POST [endpoint-base]/negotiate` request
|
||||
|
||||
The `OPTIONS [endpoint-base]` request is used to establish connection between the client and the server. The response to the `OPTIONS [endpoint-base]` request contains the `connectionId` which will be used to identify the connection on the server and the list of the transports supported by the server. The content type of the response is `application/json`. The following is a sample response to the `OPTIONS [endpoint-base]` request
|
||||
The `POST [endpoint-base]/negotiate` request is used to establish connection between the client and the server. The response to the `POST [endpoint-base]/negotiate` request contains the `connectionId` which will be used to identify the connection on the server and the list of the transports supported by the server. The content type of the response is `application/json`. The following is a sample response to the `POST [endpoint-base]/negotiate` request
|
||||
|
||||
```
|
||||
{
|
||||
|
|
@ -29,7 +29,7 @@ The `OPTIONS [endpoint-base]` request is used to establish connection between th
|
|||
|
||||
## WebSockets (Full Duplex)
|
||||
|
||||
The WebSockets transport is unique in that it is full duplex, and a persistent connection that can be established in a single operation. As a result, the client is not required to use the `OPTIONS [endpoint-base]` request to establish a connection in advance. It also includes all the necessary metadata in it's own frame metadata.
|
||||
The WebSockets transport is unique in that it is full duplex, and a persistent connection that can be established in a single operation. As a result, the client is not required to use the `POST [endpoint-base]/negotiate` request to establish a connection in advance. It also includes all the necessary metadata in it's own frame metadata.
|
||||
|
||||
The WebSocket transport is activated by making a WebSocket connection to `[endpoint-base]`. The **optional** `connectionId` query string value is used to identify the connection to attach to. If there is no `connectionId` query string value, a new connection is established. If the parameter is specified but there is no connection with the specified ID value, a `404 Not Found` response is returned. Upon receiving this request, the connection is established and the server responds with a WebSocket upgrade (`101 Switching Protocols`) immediately ready for frames to be sent/received. The WebSocket OpCode field is used to indicate the type of the frame (Text or Binary).
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ Errors while establishing the connection are handled by returning a `500 Server
|
|||
|
||||
HTTP Post is a half-transport, it is only able to send messages from the Client to the Server, as such it is **always** used with one of the other half-transports which can send from Server to Client (Server Sent Events and Long Polling).
|
||||
|
||||
This transport requires that a connection be established using the `OPTIONS [endpoint-base]` request.
|
||||
This transport requires that a connection be established using the `POST [endpoint-base]/negotiate` request.
|
||||
|
||||
The HTTP POST request is made to the URL `[endpoint-base]`. The **mandatory** `connectionId` query string value is used to identify the connection to send to. If there is no `connectionId` query string value, a `400 Bad Request` response is returned. Upon receipt of the **entire** payload, the server will process the payload and responds with `200 OK` if the payload was successfully processed. If a client makes another request to `/` while an existing request is outstanding, the new request is immediately terminated by the server with the `409 Conflict` status code.
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ If the relevant connection has been terminated, a `404 Not Found` status code is
|
|||
|
||||
## Server-Sent Events (Server-to-Client only)
|
||||
|
||||
Server-Sent Events (SSE) is a protocol specified by WHATWG at [https://html.spec.whatwg.org/multipage/comms.html#server-sent-events](https://html.spec.whatwg.org/multipage/comms.html#server-sent-events). It is capable of sending data from server to client only, so it must be paired with the HTTP Post transport. It also requires a connection already be established using the `OPTIONS [endpoint-base]` request.
|
||||
Server-Sent Events (SSE) is a protocol specified by WHATWG at [https://html.spec.whatwg.org/multipage/comms.html#server-sent-events](https://html.spec.whatwg.org/multipage/comms.html#server-sent-events). It is capable of sending data from server to client only, so it must be paired with the HTTP Post transport. It also requires a connection already be established using the `POST [endpoint-base]/negotiate` request.
|
||||
|
||||
|
||||
The protocol is similar to Long Polling in that the client opens a request to an endpoint and leaves it open. The server transmits frames as "events" using the SSE protocol. The protocol encodes a single event as a sequence of key-value pair lines, separated by `:` and using any of `\r\n`, `\n` or `\r` as line-terminators, followed by a final blank line. Keys can be duplicated and their values are concatenated with `\n`. So the following represents two events:
|
||||
|
|
@ -73,7 +73,7 @@ In this transport, the client establishes an SSE connection to `[endpoint-base]`
|
|||
|
||||
## Long Polling (Server-to-Client only)
|
||||
|
||||
Long Polling is a server-to-client half-transport, so it is always paired with HTTP Post. It requires a connection already be established using the `OPTIONS [endpoint-base]` request.
|
||||
Long Polling is a server-to-client half-transport, so it is always paired with HTTP Post. It requires a connection already be established using the `POST [endpoint-base]/negotiate` request.
|
||||
|
||||
Long Polling requires that the client poll the server for new messages. Unlike traditional polling, if there is no data available, the server will simply wait for messages to be dispatched. At some point, the server, client or an upstream proxy will likely terminate the connection, at which point the client should immediately re-send the request. Long Polling is the only transport that allows a "reconnection" where a new request can be received while the server believes an existing request is in process. This can happen because of a time out. When this happens, the existing request is immediately terminated with status code `204 No Content`. Any messages which have already been written to the existing request will be flushed and considered sent. In the case of a server side timeout with no data, a `200 OK` with a 0 `Content-Length` will be sent and the client should poll again for more data.
|
||||
|
||||
|
|
|
|||
|
|
@ -220,7 +220,14 @@ namespace Microsoft.AspNetCore.Sockets.Client
|
|||
{
|
||||
// Get a connection ID from the server
|
||||
logger.EstablishingConnection(url);
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Options, url))
|
||||
var urlBuilder = new UriBuilder(url);
|
||||
if (!urlBuilder.Path.EndsWith("/"))
|
||||
{
|
||||
urlBuilder.Path += "/";
|
||||
}
|
||||
urlBuilder.Path += "negotiate";
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Post, urlBuilder.Uri))
|
||||
{
|
||||
request.Headers.UserAgent.Add(Constants.UserAgentHeader);
|
||||
using (var response = await httpClient.SendAsync(request))
|
||||
|
|
|
|||
|
|
@ -41,12 +41,7 @@ namespace Microsoft.AspNetCore.Sockets
|
|||
return;
|
||||
}
|
||||
|
||||
if (HttpMethods.IsOptions(context.Request.Method))
|
||||
{
|
||||
// OPTIONS /{path}
|
||||
await ProcessNegotiate(context, options, logScope);
|
||||
}
|
||||
else if (HttpMethods.IsPost(context.Request.Method))
|
||||
if (HttpMethods.IsPost(context.Request.Method))
|
||||
{
|
||||
// POST /{path}
|
||||
await ProcessSend(context);
|
||||
|
|
@ -63,6 +58,29 @@ namespace Microsoft.AspNetCore.Sockets
|
|||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteNegotiateAsync(HttpContext context, HttpSocketOptions options)
|
||||
{
|
||||
// Create the log scope and the scope connectionId param will be set when the connection is created.
|
||||
var logScope = new ConnectionLogScope(connectionId: string.Empty);
|
||||
using (_logger.BeginScope(logScope))
|
||||
{
|
||||
if (!await AuthorizeHelper.AuthorizeAsync(context, options.AuthorizationData))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HttpMethods.IsPost(context.Request.Method))
|
||||
{
|
||||
// POST /{path}/negotiate
|
||||
await ProcessNegotiate(context, options, logScope);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status405MethodNotAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteEndpointAsync(HttpContext context, SocketDelegate socketDelegate, HttpSocketOptions options, ConnectionLogScope logScope)
|
||||
{
|
||||
var supportedTransports = options.Transports;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Sockets
|
|||
socketConfig(socketBuilder);
|
||||
var socket = socketBuilder.Build();
|
||||
_routes.MapRoute(path, c => _dispatcher.ExecuteAsync(c, options, socket));
|
||||
_routes.MapRoute(path + "/negotiate", c => _dispatcher.ExecuteNegotiateAsync(c, options));
|
||||
}
|
||||
|
||||
public void MapEndPoint<TEndPoint>(string path) where TEndPoint : EndPoint
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
{
|
||||
await Task.Yield();
|
||||
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
// allow DisposeAsync to continue once we know we are past the connection state check
|
||||
allowDisposeTcs.SetResult(null);
|
||||
await releaseNegotiateTcs.Task;
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -197,7 +197,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -223,7 +223,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
|
||||
return request.Method == HttpMethod.Get
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError)
|
||||
: request.Method == HttpMethod.Options
|
||||
: IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -250,7 +250,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -297,7 +297,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -353,7 +353,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -397,7 +397,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -454,7 +454,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
{
|
||||
await Task.Yield();
|
||||
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -491,14 +491,17 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
if (IsNegotiateRequest(request))
|
||||
{
|
||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse());
|
||||
}
|
||||
|
||||
if (request.Method == HttpMethod.Post)
|
||||
{
|
||||
sendTcs.SetResult(await request.Content.ReadAsByteArrayAsync());
|
||||
}
|
||||
|
||||
return request.Method == HttpMethod.Options
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
||||
var connection = new HttpConnection(new Uri("http://fakeuri.org/"), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object);
|
||||
|
|
@ -542,7 +545,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
content = "T2:T:42;";
|
||||
}
|
||||
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK, content);
|
||||
});
|
||||
|
|
@ -568,10 +571,10 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
{
|
||||
await Task.Yield();
|
||||
|
||||
return request.Method == HttpMethod.Post
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError)
|
||||
: request.Method == HttpMethod.Options
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: request.Method == HttpMethod.Post
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError)
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
||||
|
|
@ -601,7 +604,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
content = "42";
|
||||
}
|
||||
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK, content);
|
||||
});
|
||||
|
|
@ -656,7 +659,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
content = "42";
|
||||
}
|
||||
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK, content);
|
||||
});
|
||||
|
|
@ -719,7 +722,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
content = "42";
|
||||
}
|
||||
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK, content);
|
||||
});
|
||||
|
|
@ -777,7 +780,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
|
||||
return request.Method == HttpMethod.Get
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.InternalServerError)
|
||||
: request.Method == HttpMethod.Options
|
||||
: IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -893,7 +896,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return request.Method == HttpMethod.Options
|
||||
return IsNegotiateRequest(request)
|
||||
? ResponseUtils.CreateResponse(HttpStatusCode.OK, ResponseUtils.CreateNegotiationResponse())
|
||||
: ResponseUtils.CreateResponse(HttpStatusCode.OK);
|
||||
});
|
||||
|
|
@ -926,5 +929,36 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests
|
|||
Assert.NotNull(transferModeFeature);
|
||||
Assert.Equal(TransferMode.Binary, transferModeFeature.TransferMode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("http://fakeuri.org/", "http://fakeuri.org/negotiate")]
|
||||
[InlineData("http://fakeuri.org/?q=1/0", "http://fakeuri.org/negotiate?q=1/0")]
|
||||
[InlineData("http://fakeuri.org?q=1/0", "http://fakeuri.org/negotiate?q=1/0")]
|
||||
[InlineData("http://fakeuri.org/endpoint", "http://fakeuri.org/endpoint/negotiate")]
|
||||
[InlineData("http://fakeuri.org/endpoint/", "http://fakeuri.org/endpoint/negotiate")]
|
||||
[InlineData("http://fakeuri.org/endpoint?q=1/0", "http://fakeuri.org/endpoint/negotiate?q=1/0")]
|
||||
public async Task query(string requested, string expectedNegotiate)
|
||||
{
|
||||
var mockHttpHandler = new Mock<HttpMessageHandler>();
|
||||
mockHttpHandler.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Returns<HttpRequestMessage, CancellationToken>(async (request, cancellationToken) =>
|
||||
{
|
||||
await Task.Yield();
|
||||
Assert.Equal(expectedNegotiate, request.RequestUri.ToString());
|
||||
return ResponseUtils.CreateResponse(HttpStatusCode.OK,
|
||||
ResponseUtils.CreateNegotiationResponse());
|
||||
});
|
||||
|
||||
var connection = new HttpConnection(new Uri(requested), TransportType.LongPolling, loggerFactory: null, httpMessageHandler: mockHttpHandler.Object);
|
||||
await connection.StartAsync().OrTimeout();
|
||||
await connection.DisposeAsync().OrTimeout();
|
||||
}
|
||||
|
||||
private bool IsNegotiateRequest(HttpRequestMessage request)
|
||||
{
|
||||
return request.Method == HttpMethod.Post &&
|
||||
new UriBuilder(request.RequestUri).Path.EndsWith("/negotiate");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,12 +37,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
|||
services.AddOptions();
|
||||
var ms = new MemoryStream();
|
||||
context.Request.Path = "/foo";
|
||||
context.Request.Method = "OPTIONS";
|
||||
context.Request.Method = "POST";
|
||||
context.Response.Body = ms;
|
||||
var builder = new SocketBuilder(services.BuildServiceProvider());
|
||||
builder.UseEndPoint<TestEndPoint>();
|
||||
var app = builder.Build();
|
||||
await dispatcher.ExecuteAsync(context, new HttpSocketOptions(), app);
|
||||
await dispatcher.ExecuteNegotiateAsync(context, new HttpSocketOptions());
|
||||
var negotiateResponse = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(ms.ToArray()));
|
||||
var connectionId = negotiateResponse.Value<string>("connectionId");
|
||||
Assert.True(manager.TryGetConnection(connectionId, out var connectionContext));
|
||||
|
|
@ -64,12 +61,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests
|
|||
services.AddOptions();
|
||||
var ms = new MemoryStream();
|
||||
context.Request.Path = "/foo";
|
||||
context.Request.Method = "OPTIONS";
|
||||
context.Request.Method = "POST";
|
||||
context.Response.Body = ms;
|
||||
var builder = new SocketBuilder(services.BuildServiceProvider());
|
||||
builder.UseEndPoint<TestEndPoint>();
|
||||
var app = builder.Build();
|
||||
await dispatcher.ExecuteAsync(context, new HttpSocketOptions { Transports = transports }, app);
|
||||
await dispatcher.ExecuteNegotiateAsync(context, new HttpSocketOptions { Transports = transports });
|
||||
|
||||
var negotiateResponse = JsonConvert.DeserializeObject<JObject>(Encoding.UTF8.GetString(ms.ToArray()));
|
||||
var availableTransports = (TransportType)0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue