Skip to content

Commit

Permalink
[wasm] HTTP tests on NodeJS (#65303)
Browse files Browse the repository at this point in the history
- Use `node-fetch` and `node-abort-controller` polyfills.
- Always use `node_fs` on node in `fetch_like` to get runtime assets.
- Disable tests that are not supported on node.
  • Loading branch information
maraf authored Feb 17, 2022
1 parent 8e2f930 commit d2939c8
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ await ValidateClientCancellationAsync(async () =>

[Theory]
[MemberData(nameof(ThreeBools))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/65429", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJS))]
public async Task GetAsync_CancelDuringResponseBodyReceived_Unbuffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, bool readOrCopyToAsync)
{
if (LoopbackServerFactory.Version >= HttpVersion20.Value && (chunkedTransfer || connectionClose))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,11 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
request.Headers.Referrer = new Uri("http://en.wikipedia.org/wiki/Main_Page");
request.Headers.TE.Add(new TransferCodingWithQualityHeaderValue("trailers"));
request.Headers.TE.Add(new TransferCodingWithQualityHeaderValue("deflate"));
request.Headers.Trailer.Add("MyTrailer");
request.Headers.TransferEncoding.Add(new TransferCodingHeaderValue("chunked"));
if (PlatformDetection.IsNotNodeJS)
{
request.Headers.Trailer.Add("MyTrailer");
request.Headers.TransferEncoding.Add(new TransferCodingHeaderValue("chunked"));
}
if (PlatformDetection.IsNotBrowser)
{
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue("Mozilla", "5.0")));
Expand All @@ -465,8 +468,11 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
request.Headers.Add("DNT", "1 (Do Not Track Enabled)");
request.Headers.Add("X-Forwarded-For", "client1");
request.Headers.Add("X-Forwarded-For", "proxy1");
request.Headers.Add("X-Forwarded-For", "proxy2");
if (PlatformDetection.IsNotNodeJS)
{
request.Headers.Add("X-Forwarded-For", "proxy1");
request.Headers.Add("X-Forwarded-For", "proxy2");
}
request.Headers.Add("X-Forwarded-Host", "en.wikipedia.org:8080");
request.Headers.Add("X-Forwarded-Proto", "https");
request.Headers.Add("Front-End-Https", "https");
Expand All @@ -477,7 +483,10 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
request.Headers.Add("X-UIDH", "...");
request.Headers.Add("X-Csrf-Token", "i8XNjC4b8KVok4uw5RftR38Wgp2BFwql");
request.Headers.Add("X-Request-ID", "f058ebd6-02f7-4d3f-942e-904344e8cde5");
request.Headers.Add("X-Request-ID", "f058ebd6-02f7-4d3f-942e-904344e8cde5");
if (PlatformDetection.IsNotNodeJS)
{
request.Headers.Add("X-Request-ID", "f058ebd6-02f7-4d3f-942e-904344e8cde5");
}
request.Headers.Add("X-Empty", "");
request.Headers.Add("X-Null", (string)null);
request.Headers.Add("X-Underscore_Name", "X-Underscore_Name");
Expand Down Expand Up @@ -515,7 +524,10 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
Assert.Equal($"Basic {authSafeValue}", requestData.GetSingleHeaderValue("Proxy-Authorization"));
Assert.Equal("Mozilla/5.0", requestData.GetSingleHeaderValue("User-Agent"));
Assert.Equal("http://en.wikipedia.org/wiki/Main_Page", requestData.GetSingleHeaderValue("Referer"));
Assert.Equal("MyTrailer", requestData.GetSingleHeaderValue("Trailer"));
if (PlatformDetection.IsNotNodeJS)
{
Assert.Equal("MyTrailer", requestData.GetSingleHeaderValue("Trailer"));
}
Assert.Equal("1.0 fred, 1.1 example.com (Apache/1.1)", requestData.GetSingleHeaderValue("Via"));
Assert.Equal("1 (Do Not Track Enabled)", requestData.GetSingleHeaderValue("DNT"));
}
Expand All @@ -531,7 +543,15 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
Assert.Equal("bytes=500-999", requestData.GetSingleHeaderValue("Range"));
Assert.Equal("199 - \"Miscellaneous warning\"", requestData.GetSingleHeaderValue("Warning"));
Assert.Equal("XMLHttpRequest", requestData.GetSingleHeaderValue("X-Requested-With"));
Assert.Equal("client1, proxy1, proxy2", requestData.GetSingleHeaderValue("X-Forwarded-For"));
if (PlatformDetection.IsNotNodeJS)
{
Assert.Equal("client1, proxy1, proxy2", requestData.GetSingleHeaderValue("X-Forwarded-For"));
}
else
{
// node-fetch polyfill doesn't support combining multiple header values
Assert.Equal("client1", requestData.GetSingleHeaderValue("X-Forwarded-For"));
}
Assert.Equal("en.wikipedia.org:8080", requestData.GetSingleHeaderValue("X-Forwarded-Host"));
Assert.Equal("https", requestData.GetSingleHeaderValue("X-Forwarded-Proto"));
Assert.Equal("https", requestData.GetSingleHeaderValue("Front-End-Https"));
Expand All @@ -540,7 +560,15 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
Assert.Equal("http://wap.samsungmobile.com/uaprof/SGH-I777.xml", requestData.GetSingleHeaderValue("X-Wap-Profile"));
Assert.Equal("...", requestData.GetSingleHeaderValue("X-UIDH"));
Assert.Equal("i8XNjC4b8KVok4uw5RftR38Wgp2BFwql", requestData.GetSingleHeaderValue("X-Csrf-Token"));
Assert.Equal("f058ebd6-02f7-4d3f-942e-904344e8cde5, f058ebd6-02f7-4d3f-942e-904344e8cde5", requestData.GetSingleHeaderValue("X-Request-ID"));
if (PlatformDetection.IsNotNodeJS)
{
Assert.Equal("f058ebd6-02f7-4d3f-942e-904344e8cde5, f058ebd6-02f7-4d3f-942e-904344e8cde5", requestData.GetSingleHeaderValue("X-Request-ID"));
}
else
{
// node-fetch polyfill doesn't support combining multiple header values
Assert.Equal("f058ebd6-02f7-4d3f-942e-904344e8cde5", requestData.GetSingleHeaderValue("X-Request-ID"));
}
Assert.Equal("", requestData.GetSingleHeaderValue("X-Null"));
Assert.Equal("", requestData.GetSingleHeaderValue("X-Empty"));
Assert.Equal("X-Underscore_Name", requestData.GetSingleHeaderValue("X-Underscore_Name"));
Expand Down Expand Up @@ -938,6 +966,7 @@ await connection.WriteStringAsync(
[InlineData(false, true)]
[InlineData(false, false)]
[InlineData(null, false)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/65429", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJS))]
public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(bool? chunked, bool enableWasmStreaming)
{
if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public static partial class PlatformDetection
public static bool IsBrowserDomSupportedOrNotBrowser => IsNotBrowser || IsBrowserDomSupported;
public static bool IsNotBrowserDomSupported => !IsBrowserDomSupported;
public static bool IsWebSocketSupported => IsEnvironmentVariableTrue("IsWebSocketSupported");
public static bool IsNodeJS => IsEnvironmentVariableTrue("IsNodeJS");
public static bool IsNotNodeJS => !IsNodeJS;
public static bool LocalEchoServerIsNotAvailable => !LocalEchoServerIsAvailable;
public static bool LocalEchoServerIsAvailable => IsBrowser;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class HttpClientHandlerTest_Http1 : HttpClientHandlerTestBase
{
public HttpClientHandlerTest_Http1(ITestOutputHelper output) : base(output) { }

[Fact]
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNodeJS))]
public async Task SendAsync_HostHeader_First()
{
// RFC 7230 3.2.2. Field Order
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,35 @@
</ItemGroup>

<PropertyGroup Condition="'$(TargetOS)' == 'Browser'">
<!-- This doesn't run on V8 because it lacks websocket support -->
<Scenario>WasmTestOnBrowser</Scenario>
<TestArchiveTestsRoot>$(TestArchiveRoot)browseronly/</TestArchiveTestsRoot>
<TestArchiveTestsRoot>$(TestArchiveRoot)browserornodejs/</TestArchiveTestsRoot>
<TestArchiveTestsDir>$(TestArchiveTestsRoot)$(OSPlatformConfig)/</TestArchiveTestsDir>
<DefineConstants>$(DefineConstants);TARGET_BROWSER</DefineConstants>
</PropertyGroup>

<ItemGroup>
<WasmExtraFilesToDeploy Include="package.json" />
<WasmExtraFilesToDeploy Include="package-lock.json" />
</ItemGroup>

<Target Name="ProvideNpmRestoreScripts" BeforeTargets="GenerateRunScript">
<ItemGroup Condition="'$(OS)' != 'Windows_NT'">
<!-- WebSocket tests use self-signed certificates for wss protocol that are refused by NodeJS -->
<SetScriptCommands Include="if [[ &quot;$SCENARIO&quot; == &quot;WasmTestOnNodeJs&quot; || &quot;$SCENARIO&quot; == &quot;wasmtestonnodejs&quot; ]]; then export NODE_TLS_REJECT_UNAUTHORIZED=0; fi" />
<SetScriptCommands Include="if [[ &quot;$SCENARIO&quot; == &quot;WasmTestOnNodeJs&quot; || &quot;$SCENARIO&quot; == &quot;wasmtestonnodejs&quot; ]]; then export WasmXHarnessMonoArgs=&quot;$WasmXHarnessMonoArgs --setenv=NPM_MODULES=ws:WebSocket,node-fetch,node-abort-controller&quot;; fi" />
<!-- Restore NPM packages -->
<RunScriptCommands Include="if [[ &quot;$SCENARIO&quot; == &quot;WasmTestOnNodeJs&quot; || &quot;$SCENARIO&quot; == &quot;wasmtestonnodejs&quot; ]]; then npm ci; fi" />
</ItemGroup>
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
<!-- WebSocket tests use self-signed certificates for wss protocol that are refused by NodeJS -->
<SetScriptCommands Include="if /I [%SCENARIO%]==[WasmTestOnNodeJS] ( set &quot;NODE_TLS_REJECT_UNAUTHORIZED=0&quot; )" />
<SetScriptCommands Include="if /I [%SCENARIO%]==[WasmTestOnNodeJS] ( set &quot;WasmXHarnessMonoArgs=%WasmXHarnessMonoArgs% --setenv=NPM_MODULES^=ws:WebSocket,node-fetch,node-abort-controller&quot; )" />
<!-- Restore NPM packages -->
<RunScriptCommands Include="if /I [%SCENARIO%]==[WasmTestOnNodeJS] ( call npm ci )" />
</ItemGroup>
</Target>

<Import Condition="'$(TargetOS)' == 'Browser'" Project="$(CommonTestPath)System/Net/Prerequisites/LocalEchoServer.props" />

<!-- Browser specific files -->
Expand Down
118 changes: 118 additions & 0 deletions src/libraries/System.Net.Http/tests/FunctionalTests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "system.net.websockets.client.tests",
"private": true,
"dependencies": {
"node-abort-controller": "3.0.1",
"node-fetch": "2.6.7",
"ws": "8.4.0"
}
}
8 changes: 4 additions & 4 deletions src/mono/wasm/runtime/polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ let node_url: any | undefined = undefined;

export async function fetch_like(url: string): Promise<Response> {
try {
if (typeof (globalThis.fetch) === "function") {
return globalThis.fetch(url, { credentials: "same-origin" });
}
else if (ENVIRONMENT_IS_NODE) {
if (ENVIRONMENT_IS_NODE) {
if (!node_fs) {
const node_require = await requirePromise;
node_url = node_require("url");
Expand All @@ -26,6 +23,9 @@ export async function fetch_like(url: string): Promise<Response> {
json: () => JSON.parse(arrayBuffer)
};
}
else if (typeof (globalThis.fetch) === "function") {
return globalThis.fetch(url, { credentials: "same-origin" });
}
else if (typeof (read) === "function") {
// note that it can't open files with unicode names, like Straße.xml
// https://bugs.chromium.org/p/v8/issues/detail?id=12541
Expand Down
25 changes: 18 additions & 7 deletions src/mono/wasm/test-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ function processArguments(incomingArguments) {

// cheap way to let the testing infrastructure know we're running in a browser context (or not)
setenv["IsBrowserDomSupported"] = is_browser.toString().toLowerCase();
setenv["IsNodeJS"] = is_node.toString().toLowerCase();

console.log("Application arguments: " + incomingArguments.join(' '));

Expand Down Expand Up @@ -360,13 +361,23 @@ if (is_node) {
const modulesToLoad = processedArguments.setenv["NPM_MODULES"];
if (modulesToLoad) {
modulesToLoad.split(',').forEach(module => {
const parts = module.split(':');

let message = `Loading npm '${parts[0]}'`;
const moduleExport = require(parts[0]);
if (parts.length == 2) {
message += ` and attaching to global as '${parts[1]}'.`;
globalThis[parts[1]] = moduleExport;
const { 0:moduleName, 1:globalAlias } = module.split(':');

let message = `Loading npm '${moduleName}'`;
let moduleExport = require(moduleName);

if (globalAlias) {
message += ` and attaching to global as '${globalAlias}'`;
globalThis[globalAlias] = moduleExport;
} else if(moduleName == "node-fetch") {
message += ' and attaching to global';
globalThis.fetch = moduleExport.default;
globalThis.Headers = moduleExport.Headers;
globalThis.Request = moduleExport.Request;
globalThis.Response = moduleExport.Response;
} else if(moduleName == "node-abort-controller") {
message += ' and attaching to global';
globalThis.AbortController = moduleExport.AbortController;
}

console.log(message);
Expand Down

0 comments on commit d2939c8

Please sign in to comment.