diff --git a/build/dependencies.props b/build/dependencies.props index 246320791f..9ab630773a 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,58 +5,58 @@ 0.10.13 3.1.0 - 3.0.0-alpha1-10657 + 3.0.0-preview-181108-06 3.0.0-alpha1-20181011.3 1.7.3.4 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10657 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10605 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 2.2.0-rtm-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10657 - 3.0.0-alpha1-10660 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-preview-181108-06 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-alpha1-10727 + 3.0.0-preview-181108-06 + 3.0.0-alpha1-10727 4.6.0-preview1-26907-04 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10657 - 3.0.0-alpha1-10657 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10657 - 3.0.0-alpha1-10660 - 3.0.0-alpha1-10657 - 3.0.0-alpha1-10657 - 3.0.0-alpha1-10657 - 2.2.0-rtm-27023-02 + 3.0.0-preview-181109-02 + 3.0.0-preview-181109-02 + 3.0.0-preview-181109-02 + 3.0.0-alpha1-10727 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 3.0.0-preview-181108-06 + 2.2.0-rtm-27105-02 15.6.1 4.10.0 2.0.3 diff --git a/build/repo.targets b/build/repo.targets index c246ce275a..6cdce57f88 100644 --- a/build/repo.targets +++ b/build/repo.targets @@ -75,8 +75,8 @@ - - + + JavaJar $(JavaClientVersion) @@ -88,7 +88,7 @@ ship - + diff --git a/clients/ts/FunctionalTests/FunctionalTests.csproj b/clients/ts/FunctionalTests/FunctionalTests.csproj index 2de14a67d6..06948653fc 100644 --- a/clients/ts/FunctionalTests/FunctionalTests.csproj +++ b/clients/ts/FunctionalTests/FunctionalTests.csproj @@ -26,7 +26,6 @@ - diff --git a/clients/ts/FunctionalTests/Program.cs b/clients/ts/FunctionalTests/Program.cs index 0a95291396..a762f99e55 100644 --- a/clients/ts/FunctionalTests/Program.cs +++ b/clients/ts/FunctionalTests/Program.cs @@ -3,8 +3,11 @@ using System; using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Win32; namespace FunctionalTests { @@ -31,7 +34,42 @@ namespace FunctionalTests factory.AddDebug(); factory.SetMinimumLevel(LogLevel.Information); }) - .UseKestrel() + .UseKestrel((builderContext, options) => + { + options.ConfigureHttpsDefaults(httpsOptions => + { + bool useRSA = false; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Detect Win10+ + var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); + var major = key.GetValue("CurrentMajorVersionNumber") as int?; + var minor = key.GetValue("CurrentMinorVersionNumber") as int?; + + if (major.HasValue && minor.HasValue) + { + useRSA = true; + } + } + else + { + useRSA = true; + } + + if (useRSA) + { + // RSA cert, won't work on Windows 8.1 & Windows 2012 R2 using HTTP2, and ECC won't work in some Node environments + var certPath = Path.Combine(Directory.GetCurrentDirectory(), "testCert.pfx"); + httpsOptions.ServerCertificate = new X509Certificate2(certPath, "testPassword"); + } + else + { + // ECC cert, works on Windows 8.1 & Windows 2012 R2 using HTTP2 + var certPath = Path.Combine(Directory.GetCurrentDirectory(), "testCertECC.pfx"); + httpsOptions.ServerCertificate = new X509Certificate2(certPath, "testPassword"); + } + }); + }) .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup(); diff --git a/clients/ts/FunctionalTests/Startup.cs b/clients/ts/FunctionalTests/Startup.cs index 975de64581..5417be9d64 100644 --- a/clients/ts/FunctionalTests/Startup.cs +++ b/clients/ts/FunctionalTests/Startup.cs @@ -13,7 +13,9 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Connections; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; +using Microsoft.Net.Http.Headers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; @@ -32,15 +34,13 @@ namespace FunctionalTests { options.EnableDetailedErrors = true; }) - .AddJsonProtocol(options => - { - // we are running the same tests with JSON and MsgPack protocols and having - // consistent casing makes it cleaner to verify results - options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver(); - }) - .AddMessagePackProtocol(); - - services.AddCors(); + .AddJsonProtocol(options => + { + // we are running the same tests with JSON and MsgPack protocols and having + // consistent casing makes it cleaner to verify results + options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver(); + }) + .AddMessagePackProtocol(); services.AddAuthorization(options => { @@ -90,12 +90,36 @@ namespace FunctionalTests app.UseFileServer(); - app.UseCors(policyBuilder => + // Custom CORS to allow any origin + credentials (which isn't allowed by the CORS spec) + // This is for testing purposes only (karma hosts the client on its own server), never do this in production + app.Use((context, next) => { - policyBuilder.AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials(); + var originHeader = context.Request.Headers[HeaderNames.Origin]; + if (!StringValues.IsNullOrEmpty(originHeader)) + { + context.Response.Headers[HeaderNames.AccessControlAllowOrigin] = originHeader; + context.Response.Headers[HeaderNames.AccessControlAllowCredentials] = "true"; + + var requestMethod = context.Request.Headers[HeaderNames.AccessControlRequestMethod]; + if (!StringValues.IsNullOrEmpty(requestMethod)) + { + context.Response.Headers[HeaderNames.AccessControlAllowMethods] = requestMethod; + } + + var requestHeaders = context.Request.Headers[HeaderNames.AccessControlRequestHeaders]; + if (!StringValues.IsNullOrEmpty(requestHeaders)) + { + context.Response.Headers[HeaderNames.AccessControlAllowHeaders] = requestHeaders; + } + } + + if (string.Equals(context.Request.Method, "OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + context.Response.StatusCode = StatusCodes.Status204NoContent; + return Task.CompletedTask; + } + + return next.Invoke(); }); app.UseConnections(routes => diff --git a/clients/ts/FunctionalTests/scripts/karma.local.conf.js b/clients/ts/FunctionalTests/scripts/karma.local.conf.js index 3c127435b9..fdcb9b779b 100644 --- a/clients/ts/FunctionalTests/scripts/karma.local.conf.js +++ b/clients/ts/FunctionalTests/scripts/karma.local.conf.js @@ -57,8 +57,9 @@ try { ChromeHeadlessNoSandbox: { base: 'ChromeHeadless', + // Ignore cert errors to allow our test cert to work (NEVER do this outside of testing) // ChromeHeadless runs about 10x slower on Windows 7 machines without the --proxy switches below. Why? ¯\_(ツ)_/¯ - flags: ["--no-sandbox", "--proxy-server='direct://'", "--proxy-bypass-list=*"] + flags: ["--no-sandbox", "--proxy-server='direct://'", "--proxy-bypass-list=*", "--allow-insecure-localhost", "--ignore-certificate-errors"] } }, }); diff --git a/clients/ts/FunctionalTests/scripts/run-tests.ts b/clients/ts/FunctionalTests/scripts/run-tests.ts index 3fcda8945a..7dd600eded 100644 --- a/clients/ts/FunctionalTests/scripts/run-tests.ts +++ b/clients/ts/FunctionalTests/scripts/run-tests.ts @@ -33,11 +33,12 @@ setTimeout(() => { process.exit(1); }, 1000 * 60 * 10); -function waitForMatch(command: string, process: ChildProcess, regex: RegExp): Promise { - return new Promise((resolve, reject) => { +function waitForMatches(command: string, process: ChildProcess, regex: RegExp, matchCount: number): Promise { + return new Promise((resolve, reject) => { const commandDebug = _debug(`${command}`); try { let lastLine = ""; + let results: string[] = null; async function onData(this: Readable, chunk: string | Buffer): Promise { try { @@ -50,15 +51,23 @@ function waitForMatch(command: string, process: ChildProcess, regex: RegExp): Pr lastLine = ""; chunk = chunk.substring(lineEnd + EOL.length); + const res = regex.exec(chunkLine); + if (results == null && res != null) { + results = res; + } else if (res != null) { + results = Array().concat(results, res); + } - const results = regex.exec(chunkLine); - commandDebug(chunkLine); - if (results && results.length > 0) { + // * 2 because each match will have the original line plus the match + if (results && results.length >= matchCount * 2) { resolve(results); return; } + + commandDebug(chunkLine); lineEnd = chunk.indexOf(EOL); } + lastLine = chunk.toString(); } catch (e) { this.removeAllListeners("data"); @@ -158,15 +167,17 @@ function runKarma(karmaConfig) { }); } -function runJest(url: string) { +function runJest(httpsUrl: string, httpUrl: string) { const jestPath = path.resolve(__dirname, "..", "..", "common", "node_modules", "jest", "bin", "jest.js"); const configPath = path.resolve(__dirname, "..", "func.jest.config.js"); console.log("Starting Node tests using Jest."); return new Promise((resolve, reject) => { const logStream = fs.createWriteStream(path.resolve(__dirname, "..", "..", "..", "..", "artifacts", "logs", "node.functionaltests.log")); - const p = exec(`"${process.execPath}" "${jestPath}" --config "${configPath}"`, { env: { SERVER_URL: url }, timeout: 200000, maxBuffer: 10 * 1024 * 1024 }, + // Use NODE_TLS_REJECT_UNAUTHORIZED to allow our test cert to be used by the Node tests (NEVER use this environment variable outside of testing) + const p = exec(`"${process.execPath}" "${jestPath}" --config "${configPath}"`, { env: { SERVER_URL: `${httpsUrl};${httpUrl}`, NODE_TLS_REJECT_UNAUTHORIZED: 0 }, timeout: 200000, maxBuffer: 10 * 1024 * 1024 }, (error: any, stdout, stderr) => { + console.log("Finished Node tests."); if (error) { console.log(error.message); return resolve(error.code); @@ -183,7 +194,7 @@ function runJest(url: string) { const serverPath = path.resolve(__dirname, "..", "bin", configuration, "netcoreapp2.2", "FunctionalTests.dll"); debug(`Launching Functional Test Server: ${serverPath}`); - let desiredServerUrl = "http://127.0.0.1:0"; + let desiredServerUrl = "https://127.0.0.1:0;http://127.0.0.1:0"; if (sauce) { // SauceLabs can only proxy certain ports for Edge and Safari. @@ -212,11 +223,12 @@ function runJest(url: string) { process.on("exit", cleanup); debug("Waiting for Functional Test Server to start"); - const matches = await waitForMatch("dotnet", dotnet, /Now listening on: (http:\/\/[^\/]+:[\d]+)/); - const url = matches[1]; - debug(`Functional Test Server has started at ${url}`); + const matches = await waitForMatches("dotnet", dotnet, /Now listening on: (https?:\/\/[^\/]+:[\d]+)/, 2); + const httpsUrl = matches[1]; + const httpUrl = matches[3]; + debug(`Functional Test Server has started at ${httpsUrl} and ${httpUrl}`); - debug(`Using SignalR Server: ${url}`); + debug(`Using SignalR Server: ${httpsUrl} and ${httpUrl}`); // Start karma server const conf = { @@ -238,9 +250,9 @@ function runJest(url: string) { } // Pass server URL to tests - conf.client.args = ["--server", url]; + conf.client.args = ["--server", `${httpsUrl};${httpUrl}`]; - const jestExit = await runJest(url); + const jestExit = await runJest(httpsUrl, httpUrl); // Check if we got any browsers let karmaExit; diff --git a/clients/ts/FunctionalTests/testCert.pfx b/clients/ts/FunctionalTests/testCert.pfx new file mode 100644 index 0000000000..7118908c2d Binary files /dev/null and b/clients/ts/FunctionalTests/testCert.pfx differ diff --git a/clients/ts/FunctionalTests/testCertECC.pfx b/clients/ts/FunctionalTests/testCertECC.pfx new file mode 100644 index 0000000000..888ccb032a Binary files /dev/null and b/clients/ts/FunctionalTests/testCertECC.pfx differ diff --git a/clients/ts/FunctionalTests/ts/Common.ts b/clients/ts/FunctionalTests/ts/Common.ts index 13062d97de..37b9b532fb 100644 --- a/clients/ts/FunctionalTests/ts/Common.ts +++ b/clients/ts/FunctionalTests/ts/Common.ts @@ -5,27 +5,35 @@ import { HttpTransportType, IHubProtocol, JsonHubProtocol } from "@aspnet/signal import { MessagePackHubProtocol } from "@aspnet/signalr-protocol-msgpack"; export let ENDPOINT_BASE_URL: string; +export let ENDPOINT_BASE_HTTPS_URL: string; if (typeof window !== "undefined" && (window as any).__karma__) { const args = (window as any).__karma__.config.args as string[]; - let server = ""; + let httpsServer = ""; + let httpServer = ""; for (let i = 0; i < args.length; i += 1) { switch (args[i]) { case "--server": i += 1; - server = args[i]; + const urls = args[i].split(";"); + httpsServer = urls[0]; + httpServer = urls[1]; + console.log(httpServer); break; } } // Running in Karma? Need to use an absolute URL - ENDPOINT_BASE_URL = server; + ENDPOINT_BASE_URL = httpServer; + ENDPOINT_BASE_HTTPS_URL = httpsServer; console.log(`Using SignalR Server: ${ENDPOINT_BASE_URL}`); } else if (typeof document !== "undefined") { ENDPOINT_BASE_URL = `${document.location.protocol}//${document.location.host}`; } else if (process && process.env && process.env.SERVER_URL) { - ENDPOINT_BASE_URL = process.env.SERVER_URL; + const urls = process.env.SERVER_URL.split(";"); + ENDPOINT_BASE_HTTPS_URL = urls[0]; + ENDPOINT_BASE_URL = urls[1]; } else { throw new Error("The server could not be found."); } diff --git a/clients/ts/FunctionalTests/ts/HubConnectionTests.ts b/clients/ts/FunctionalTests/ts/HubConnectionTests.ts index 8d54c23884..b7665cb306 100644 --- a/clients/ts/FunctionalTests/ts/HubConnectionTests.ts +++ b/clients/ts/FunctionalTests/ts/HubConnectionTests.ts @@ -7,11 +7,12 @@ import { AbortError, DefaultHttpClient, HttpClient, HttpRequest, HttpResponse, HttpTransportType, HubConnectionBuilder, IHttpConnectionOptions, JsonHubProtocol, NullLogger } from "@aspnet/signalr"; import { MessagePackHubProtocol } from "@aspnet/signalr-protocol-msgpack"; -import { eachTransport, eachTransportAndProtocol, ENDPOINT_BASE_URL } from "./Common"; +import { eachTransport, eachTransportAndProtocol, ENDPOINT_BASE_HTTPS_URL, ENDPOINT_BASE_URL } from "./Common"; import "./LogBannerReporter"; import { TestLogger } from "./TestLogger"; const TESTHUBENDPOINT_URL = ENDPOINT_BASE_URL + "/testhub"; +const TESTHUBENDPOINT_HTTPS_URL = ENDPOINT_BASE_HTTPS_URL + "/testhub"; const TESTHUB_NOWEBSOCKETS_ENDPOINT_URL = ENDPOINT_BASE_URL + "/testhub-nowebsockets"; // On slower CI machines, these tests sometimes take longer than 5s @@ -62,6 +63,35 @@ describe("hubConnection", () => { }); }); + // Run test in Node or Chrome, but not on macOS + if ((process && process.platform !== "darwin") && (typeof navigator === "undefined" || navigator.userAgent.search("Chrome") !== -1)) { + it("using https, can invoke server method and receive result", (done) => { + const message = "你好,世界!"; + + const hubConnection = getConnectionBuilder(transportType, TESTHUBENDPOINT_HTTPS_URL) + .withHubProtocol(protocol) + .build(); + + hubConnection.onclose((error) => { + expect(error).toBeUndefined(); + done(); + }); + + hubConnection.start().then(() => { + hubConnection.invoke("Echo", message).then((result) => { + expect(result).toBe(message); + }).catch((e) => { + fail(e); + }).then(() => { + hubConnection.stop(); + }); + }).catch((e) => { + fail(e); + done(); + }); + }); + } + it("can invoke server method non-blocking and not receive result", (done) => { const message = "你好,世界!";