Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.microsoft.azure.functions.worker;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Locale;
import java.util.logging.*;
import javax.annotation.*;

Expand Down Expand Up @@ -157,7 +157,8 @@ public int getPort() {
return this.port;
}

public String getUri() {
@Override
public String getFunctionsUri() {
return this.uri;
}

Expand Down Expand Up @@ -185,17 +186,44 @@ private boolean isCommandlineValid() {

private String parseUri(String uri) throws ParseException {
try {
URL url = new URL(uri);
url.toURI();
this.host = url.getHost();
this.port = url.getPort();
URI parsedUri = new URI(uri);
String host = parsedUri.getHost();
String scheme = parsedUri.getScheme();
int port = parsedUri.getPort();

if (scheme == null || scheme.isEmpty()) {
throw new IllegalArgumentException("URI scheme is missing");
}

switch (scheme.toLowerCase(Locale.ROOT)) {
case "http":
if (port == -1) {
port = 80;
}
break;
case "https":
if (port == -1) {
port = 443;
}
break;
default:
throw new IllegalArgumentException("Unsupported URI scheme");
}

if (host == null || host.isEmpty()) {
throw new IllegalArgumentException("URI host is missing");
}

if (port < 1 || port > 65535) {
throw new IndexOutOfBoundsException("port number out of range");
}

this.host = host;
this.port = port;
return uri;
} catch (MalformedURLException | URISyntaxException | IndexOutOfBoundsException e) {
} catch (URISyntaxException | IllegalArgumentException | IndexOutOfBoundsException e) {
throw new ParseException(String.format(
"Error parsing URI \"%s\". Please provide a valid URI", uri));
"Error parsing URI \"%s\". Please provide a valid http or https URI", uri));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ public interface IApplication {
String getHost();
int getPort();
Integer getMaxMessageSize();
default String getFunctionsUri() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import java.util.concurrent.atomic.*;
import java.util.function.*;
import java.util.logging.*;
import javax.annotation.*;
import javax.annotation.PostConstruct;

import io.grpc.*;
import io.grpc.stub.*;
Expand All @@ -24,7 +24,12 @@ public class JavaWorkerClient implements AutoCloseable {

public JavaWorkerClient(IApplication app) {
WorkerLogManager.initialize(this, app.logToConsole());
ManagedChannelBuilder<?> chanBuilder = ManagedChannelBuilder.forAddress(app.getHost(), app.getPort()).usePlaintext();
ManagedChannelBuilder<?> chanBuilder = ManagedChannelBuilder.forAddress(app.getHost(), app.getPort());
if (useTransportSecurity(app.getFunctionsUri())) {
chanBuilder.useTransportSecurity();
} else {
chanBuilder.usePlaintext();
}
chanBuilder.maxInboundMessageSize(Integer.MAX_VALUE);

this.channel = chanBuilder.build();
Expand Down Expand Up @@ -138,4 +143,12 @@ private synchronized void send(String requestId, MessageHandler<?, ?> marshaller
private final AtomicReference<StreamingMessagePeer> peer;
private final Map<StreamingMessage.ContentCase, Supplier<MessageHandler<?, ?>>> handlerSuppliers;
private final ClassLoaderProvider classPathProvider;

/**
* @param functionsUri Host endpoint URI, or null for legacy startup args that only provide host and port.
*/
static boolean useTransportSecurity(String functionsUri) {
return functionsUri != null
&& functionsUri.regionMatches(true, 0, "https://", 0, "https://".length());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.microsoft.azure.functions.worker;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ApplicationTest {
@Test
public void legacyImplementationsDefaultFunctionsUriToNull() {
IApplication application = new LegacyApplication();

assertNull(application.getFunctionsUri());
}

@Test
public void prefixedStartupArgumentsTakePrecedenceOverLegacyValues() throws Exception {
Application application = createApplication(
"--functions-uri", "https://functions.example:8443",
"--host", "legacy.example",
"--port", "7071",
"--functions-worker-id", "prefixed-worker",
"--workerId", "legacy-worker",
"--functions-request-id", "prefixed-request",
"--requestId", "legacy-request",
"--functions-grpc-max-message-length", "2048",
"--grpcMaxMessageLength", "1024");

assertTrue(isCommandLineValid(application));
assertEquals("https://functions.example:8443", application.getFunctionsUri());
assertEquals("functions.example", application.getHost());
assertEquals(8443, application.getPort());
assertEquals(Integer.valueOf(2048), application.getMaxMessageSize());
assertEquals("prefixed-worker", invokePrivateStringAccessor(application, "getWorkerId"));
assertEquals("prefixed-request", invokePrivateStringAccessor(application, "getRequestId"));
}

@Test
public void legacyHostAndPortRemainAvailableWhenFunctionsUriIsAbsent() throws Exception {
Application application = createApplication(
"--host", "localhost",
"--port", "7001",
"--workerId", "legacy-worker",
"--requestId", "legacy-request",
"--grpcMaxMessageLength", "4096");

assertTrue(isCommandLineValid(application));
assertNull(application.getFunctionsUri());
assertEquals("localhost", application.getHost());
assertEquals(7001, application.getPort());
assertEquals(Integer.valueOf(4096), application.getMaxMessageSize());
}

@Test
public void httpsFunctionsUriDefaultsToPort443WhenPortIsMissing() throws Exception {
Application application = createApplication(
"--functions-uri", "https://functions.example/path",
"--functions-worker-id", "prefixed-worker",
"--functions-request-id", "prefixed-request",
"--functions-grpc-max-message-length", "2048");

assertTrue(isCommandLineValid(application));
assertEquals("functions.example", application.getHost());
assertEquals(443, application.getPort());
}

@Test
public void httpFunctionsUriDefaultsToPort80WhenPortIsMissing() throws Exception {
Application application = createApplication(
"--functions-uri", "http://functions.example/path",
"--functions-worker-id", "prefixed-worker",
"--functions-request-id", "prefixed-request",
"--functions-grpc-max-message-length", "2048");

assertTrue(isCommandLineValid(application));
assertEquals("functions.example", application.getHost());
assertEquals(80, application.getPort());
}

@Test
public void unsupportedFunctionsUriSchemeFailsCommandLineValidation() throws Exception {
Application application = createApplication(
"--functions-uri", "unix:///tmp/functions.sock",
"--functions-worker-id", "prefixed-worker",
"--functions-request-id", "prefixed-request",
"--functions-grpc-max-message-length", "2048");

assertFalse(isCommandLineValid(application));
}

private static Application createApplication(String... args) throws Exception {
Constructor<Application> constructor = Application.class.getDeclaredConstructor(String[].class);
constructor.setAccessible(true);
return constructor.newInstance((Object) args);
}

private static boolean isCommandLineValid(Application application) throws Exception {
Method method = Application.class.getDeclaredMethod("isCommandlineValid");
method.setAccessible(true);
return (boolean) method.invoke(application);
}

private static String invokePrivateStringAccessor(Application application, String methodName) throws Exception {
Method method = Application.class.getDeclaredMethod(methodName);
method.setAccessible(true);
return (String) method.invoke(application);
}

private static final class LegacyApplication implements IApplication {
@Override
public boolean logToConsole() {
return false;
}

@Override
public String getHost() {
return "localhost";
}

@Override
public int getPort() {
return 7071;
}

@Override
public Integer getMaxMessageSize() {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.microsoft.azure.functions.worker;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class JavaWorkerClientTest {
@Test
public void legacyStartupDefaultsToPlaintextTransport() {
assertFalse(JavaWorkerClient.useTransportSecurity(null));
}

@Test
public void httpFunctionsUriUsesPlaintextTransport() {
assertFalse(JavaWorkerClient.useTransportSecurity("http://functions.example:7071"));
}

@Test
public void httpsFunctionsUriUsesTransportSecurity() {
assertTrue(JavaWorkerClient.useTransportSecurity("https://functions.example:8443"));
}

@Test
public void httpsFunctionsUriSchemeIsCaseInsensitive() {
assertTrue(JavaWorkerClient.useTransportSecurity("HTTPS://functions.example:8443"));
}
}
Loading