Skip to content

Commit

Permalink
Webclient redesign (#7255)
Browse files Browse the repository at this point in the history
* WebClient, HTTP/1.1 webclient aligned
* HTTP/2 webclient alignment (in progress)
* Fix for #7223
* Use UriInfo in WebClient, combined query and fragment with it
* Cookie support for webclient.
* WebSocket now upgrades through API
* HTTP/2 with services.
* Typed client response fixes (so it does not need to be autocloseable)
* Fix client entity reading, when chunked received over the network is bigger than buffer used to read it.
* Fixed error of not draining request when continue was already sent.
* Fix possible mutation of internal state of HeaderValue that is shared.

Co-authored-by: Santiago Pericas-Geertsen <santiago.pericasgeertsen@oracle.com>
Co-authored-by: Tomas Langer <tomas.langer@oracle.com>
  • Loading branch information
tomas-langer and spericas committed Jul 31, 2023
1 parent 6473a4f commit 39b644b
Show file tree
Hide file tree
Showing 344 changed files with 10,738 additions and 6,324 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package {{package}};

import io.helidon.common.http.Http;
import io.helidon.nima.webclient.WebClient;
import io.helidon.nima.webclient.http1.Http1Client;
import io.helidon.nima.webclient.api.WebClient;

/**
* Executable class that invokes HTTP/1 requests against the server.
Expand All @@ -17,19 +16,17 @@ public class GreetClientHttp {
* @param args ignored
*/
public static void main(String[] args) {
Http1Client client = WebClient.builder()
WebClient client = WebClient.builder()
.baseUri("http://localhost:8080/greet")
.build();
String response = client.method(Http.Method.GET)
.request()
.as(String.class);
.requestEntity(String.class);
System.out.println(response);
response = client.get("Nima")
.request()
.as(String.class);
.requestEntity(String.class);
System.out.println(response);
}
Expand Down
20 changes: 20 additions & 0 deletions bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1095,11 +1095,31 @@
<artifactId>helidon-nima-webserver-static-content</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.nima.webclient</groupId>
<artifactId>helidon-nima-webclient-api</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.nima.webclient</groupId>
<artifactId>helidon-nima-webclient-http1</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.nima.webclient</groupId>
<artifactId>helidon-nima-webclient</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.nima.webclient.dns.resolver</groupId>
<artifactId>helidon-nima-webclient-dns-resolver-first</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.nima.webclient.dns.resolver</groupId>
<artifactId>helidon-nima-webclient-dns-resolver-round-robin</artifactId>
<version>${helidon.version}</version>
</dependency>
<dependency>
<groupId>io.helidon.nima.webclient</groupId>
<artifactId>helidon-nima-webclient-security</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,10 @@ private void generatePrototypeWithBuilder(TypeElement builderInterface,
pw.println(javadocLine);
}
pw.println(" *");
pw.println(" * @see #builder()");
if (!propertyData.hasRequired() && blueprintDef.createEmptyPublic()) {
if (blueprintDef.builderPublic()) {
pw.println(" * @see #builder()");
}
if (!propertyData.hasRequired() && blueprintDef.createEmptyPublic() && blueprintDef.builderPublic()) {
pw.println(" * @see #create()");
}
pw.println(" */");
Expand Down Expand Up @@ -438,7 +440,7 @@ static X create(Config config)
pw.println();
}

if (blueprintDef.createEmptyPublic()) {
if (blueprintDef.createEmptyPublic() && blueprintDef.builderPublic()) {
/*
static X create()
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ static void generate(PrintWriter pw,
pw.println("> {");
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.println("private Builder() {");
if (typeContext.blueprintData().builderPublic()) {
pw.println("private Builder() {");
} else {
// package private to allow instantiation
pw.println("Builder() {");
}
pw.print(SOURCE_SPACING);
pw.print(SOURCE_SPACING);
pw.println("}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,16 +300,14 @@ private static void gatherExtends(TypeInfo typeInfo, Set<TypeName> extendList,
// this is a prototype itself, ignore additional interfaces
gatherAll = false;
superPrototypes.add(info.typeName());
ignoredInterfaces.add(info.typeName());
ignoredInterfaces.add(TypeName.builder(info.typeName())
.className(info.typeName().className() + "Blueprint")
.build());
// also add all super interfaces of the prototype
info.interfaceTypeInfo()
.stream()
.map(TypeInfo::typeName)
.map(TypeName::genericTypeName)
.forEach(ignoredInterfaces::add);

// we need to ignore ANY interface implemented by "info" and its super interfaces
if (ignoredInterfaces.add(info.typeName().genericTypeName())) {
ignoredInterfaces.add(TypeName.builder(info.typeName())
.className(info.typeName().className() + "Blueprint")
.build());
ignoreAllInterfaces(ignoredInterfaces, info);
}
break;
}
}
Expand All @@ -319,6 +317,17 @@ private static void gatherExtends(TypeInfo typeInfo, Set<TypeName> extendList,
}
}

private static void ignoreAllInterfaces(Set<TypeName> ignoredInterfaces, TypeInfo info) {
// also add all super interfaces of the prototype
List<TypeInfo> superIfaces = info.interfaceTypeInfo();

for (TypeInfo superIface : superIfaces) {
if (ignoredInterfaces.add(superIface.typeName().genericTypeName())) {
ignoreAllInterfaces(ignoredInterfaces, superIface);
}
}
}

@SuppressWarnings("checkstyle:ParameterNumber") // we need all of them
private static void gatherBuilderProperties(ProcessingContext processingContext,
TypeInfo typeInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ TypeName actualType() {

@Override
Optional<String> generateFromConfig(PrototypeProperty.ConfiguredOption configured, FactoryMethods factoryMethods) {
if (STRING_TYPE.equals(actualType)) {
return Optional.of("config.get(\"" + configured.configKey() + "\").asMap().ifPresent(this::" + setterName() + ");");
}

return Optional.of("config.get(\"" + configured.configKey() + "\").asNodeList().ifPresent(nodes -> nodes.forEach"
+ "(node -> "
+ name() + ".put(node.get(\"name\").asString().orElse(node.name()), node"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -210,7 +210,7 @@ static BufferData create(String stringValue) {
* Read bytes from the input stream.
* Reads at least 1 byte.
* @param in input stream
* @return number of bytes read
* @return number of bytes read, -1 if the input stream is finished
*/
int readFrom(InputStream in);

Expand Down Expand Up @@ -574,7 +574,7 @@ default BufferData copy() {
/**
* Number of bytes available for reading.
*
* @return available byte
* @return available bytes
*/
int available();

Expand Down Expand Up @@ -642,4 +642,16 @@ default void writeAscii(String text) {
* @return byte at the index
*/
int get(int index);

/**
* Read the content of this data as bytes.
* This method always creates a new byte array.
*
* @return byte array with {@link #available()} bytes, may be empty
*/
default byte[] readBytes() {
byte[] bytes = new byte[available()];
read(bytes);
return bytes;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates.
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -163,11 +163,17 @@ public T remove(HeaderName name, Consumer<HeaderValue> removedConsumer) {
@Override
public T set(HeaderValue header) {
HeaderName name = header.headerName();

HeaderValue usedHeader = header;
if (header instanceof HeaderValueWriteable) {
// we must create a new instance, as we risk modifying state of the provided header
usedHeader = new HeaderValueCopy(header);
}
int index = name.index();
if (index == -1) {
customHeaders.put(name, header);
customHeaders.put(name, usedHeader);
} else {
knownHeaders[index] = header;
knownHeaders[index] = usedHeader;
knownHeaderIndices.add(index);
}
return (T) this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import io.helidon.common.config.Config;
import io.helidon.common.configurable.AllowList;
Expand Down Expand Up @@ -289,7 +288,7 @@ public UriInfo uriInfo(String remoteAddress,
String requestPath,
ServerRequestHeaders headers,
UriQuery query,
boolean isSecure) {
boolean isSecure) {
String scheme = null;
String authority = null;
String host = null;
Expand Down Expand Up @@ -340,34 +339,28 @@ public UriInfo uriInfo(String remoteAddress,
}
}

UriInfo.Builder uriInfo = UriInfo.builder();

// now we must fill values that were not discovered (to have a valid URI information)
if (host == null && authority == null) {
authority = headers.first(Http.Header.HOST).orElse(null);
}

if (path == null) {
path = requestPath;
uriInfo.path(path == null ? requestPath : path);
uriInfo.host(localAddress); // this is the fallback
if (authority != null) {
uriInfo.authority(authority); // this is the second possibility
}

if (host == null && authority != null) {
Authority a;
if (scheme == null) {
a = Authority.create(authority);
} else {
a = Authority.create(scheme, authority);
}
if (a.host() != null) {
host = a.host();
}
if (port == -1) {
port = a.port();
}
if (host != null) {
uriInfo.host(host); // and this one has priority
}
if (port != -1) {
uriInfo.port(port);
}

/*
Discover final values to be used
*/

/*
Discover final values to be used
*/
if (scheme == null) {
if (port == 80) {
scheme = "http";
Expand All @@ -377,23 +370,10 @@ public UriInfo uriInfo(String remoteAddress,
scheme = isSecure ? "https" : "http";
}
}
uriInfo.scheme(scheme);
uriInfo.query(query);

if (host == null) {
host = localAddress;
}

// we may still have -1, if port was not explicitly defined by a header - use default port of protocol
if (port == -1) {
if ("https".equals(scheme)) {
port = 443;
} else {
port = 80;
}
}
if (query == null || query.isEmpty()) {
query = null;
}
return new UriInfo(scheme, host, port, path, Optional.ofNullable(query));
return uriInfo.build();
}

private static String hostPart(String address) {
Expand Down Expand Up @@ -467,29 +447,6 @@ private XForwardedDiscovery discoverUsingXForwarded(ServerRequestHeaders headers
return discovered ? new XForwardedDiscovery(scheme, host, port, path) : null;
}

private record Authority(String host, int port) {
static Authority create(String hostHeader) {
int colon = hostHeader.indexOf(':');
if (colon == -1) {
// we do not know the protocol, and there is no port defined
return new Authority(hostHeader, -1);
}
String hostString = hostHeader.substring(0, colon);
String portString = hostHeader.substring(colon + 1);
return new Authority(hostString, Integer.parseInt(portString));
}
static Authority create(String scheme, String hostHeader) {
int colon = hostHeader.indexOf(':');
if (colon == -1) {
// define port by protocol
return new Authority(hostHeader, "https".equals(scheme) ? 443 : 80);
}
String hostString = hostHeader.substring(0, colon);
String portString = hostHeader.substring(colon + 1);
return new Authority(hostString, Integer.parseInt(portString));
}
}

private record ForwardedDiscovery(String authority, String scheme) {}
private record XForwardedDiscovery(String scheme, String host, int port, String path) {}

Expand Down
Loading

0 comments on commit 39b644b

Please sign in to comment.