Change negotiate to POST (#1122)

This commit is contained in:
BrennanConroy 2017-11-17 15:31:47 -08:00 committed by GitHub
parent 046553cfe4
commit 93cbf4dbef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 172 additions and 68 deletions

View File

@ -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]));
}

View File

@ -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> {

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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.

View File

@ -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))

View File

@ -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;

View File

@ -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

View File

@ -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");
}
}
}

View File

@ -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;