Skip to content

Commit 39b644b

Browse files
Webclient redesign (helidon-io#7255)
* WebClient, HTTP/1.1 webclient aligned * HTTP/2 webclient alignment (in progress) * Fix for helidon-io#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>
1 parent 6473a4f commit 39b644b

344 files changed

Lines changed: 10738 additions & 6324 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

archetypes/helidon/src/main/archetype/nima/quickstart/files/src/main/java/__pkg__/GreetClientHttp.java.mustache

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package {{package}};
22

33
import io.helidon.common.http.Http;
4-
import io.helidon.nima.webclient.WebClient;
5-
import io.helidon.nima.webclient.http1.Http1Client;
4+
import io.helidon.nima.webclient.api.WebClient;
65

76
/**
87
* Executable class that invokes HTTP/1 requests against the server.
@@ -17,19 +16,17 @@ public class GreetClientHttp {
1716
* @param args ignored
1817
*/
1918
public static void main(String[] args) {
20-
Http1Client client = WebClient.builder()
19+
WebClient client = WebClient.builder()
2120
.baseUri("http://localhost:8080/greet")
2221
.build();
2322
2423
String response = client.method(Http.Method.GET)
25-
.request()
26-
.as(String.class);
24+
.requestEntity(String.class);
2725
2826
System.out.println(response);
2927
3028
response = client.get("Nima")
31-
.request()
32-
.as(String.class);
29+
.requestEntity(String.class);
3330
3431
System.out.println(response);
3532
}

bom/pom.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,11 +1095,31 @@
10951095
<artifactId>helidon-nima-webserver-static-content</artifactId>
10961096
<version>${helidon.version}</version>
10971097
</dependency>
1098+
<dependency>
1099+
<groupId>io.helidon.nima.webclient</groupId>
1100+
<artifactId>helidon-nima-webclient-api</artifactId>
1101+
<version>${helidon.version}</version>
1102+
</dependency>
1103+
<dependency>
1104+
<groupId>io.helidon.nima.webclient</groupId>
1105+
<artifactId>helidon-nima-webclient-http1</artifactId>
1106+
<version>${helidon.version}</version>
1107+
</dependency>
10981108
<dependency>
10991109
<groupId>io.helidon.nima.webclient</groupId>
11001110
<artifactId>helidon-nima-webclient</artifactId>
11011111
<version>${helidon.version}</version>
11021112
</dependency>
1113+
<dependency>
1114+
<groupId>io.helidon.nima.webclient.dns.resolver</groupId>
1115+
<artifactId>helidon-nima-webclient-dns-resolver-first</artifactId>
1116+
<version>${helidon.version}</version>
1117+
</dependency>
1118+
<dependency>
1119+
<groupId>io.helidon.nima.webclient.dns.resolver</groupId>
1120+
<artifactId>helidon-nima-webclient-dns-resolver-round-robin</artifactId>
1121+
<version>${helidon.version}</version>
1122+
</dependency>
11031123
<dependency>
11041124
<groupId>io.helidon.nima.webclient</groupId>
11051125
<artifactId>helidon-nima-webclient-security</artifactId>

builder/processor/src/main/java/io/helidon/builder/processor/BlueprintProcessor.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,10 @@ private void generatePrototypeWithBuilder(TypeElement builderInterface,
293293
pw.println(javadocLine);
294294
}
295295
pw.println(" *");
296-
pw.println(" * @see #builder()");
297-
if (!propertyData.hasRequired() && blueprintDef.createEmptyPublic()) {
296+
if (blueprintDef.builderPublic()) {
297+
pw.println(" * @see #builder()");
298+
}
299+
if (!propertyData.hasRequired() && blueprintDef.createEmptyPublic() && blueprintDef.builderPublic()) {
298300
pw.println(" * @see #create()");
299301
}
300302
pw.println(" */");
@@ -438,7 +440,7 @@ static X create(Config config)
438440
pw.println();
439441
}
440442

441-
if (blueprintDef.createEmptyPublic()) {
443+
if (blueprintDef.createEmptyPublic() && blueprintDef.builderPublic()) {
442444
/*
443445
static X create()
444446
*/

builder/processor/src/main/java/io/helidon/builder/processor/GenerateBuilder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,12 @@ static void generate(PrintWriter pw,
7070
pw.println("> {");
7171
pw.print(SOURCE_SPACING);
7272
pw.print(SOURCE_SPACING);
73-
pw.println("private Builder() {");
73+
if (typeContext.blueprintData().builderPublic()) {
74+
pw.println("private Builder() {");
75+
} else {
76+
// package private to allow instantiation
77+
pw.println("Builder() {");
78+
}
7479
pw.print(SOURCE_SPACING);
7580
pw.print(SOURCE_SPACING);
7681
pw.println("}");

builder/processor/src/main/java/io/helidon/builder/processor/TypeContext.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -300,16 +300,14 @@ private static void gatherExtends(TypeInfo typeInfo, Set<TypeName> extendList,
300300
// this is a prototype itself, ignore additional interfaces
301301
gatherAll = false;
302302
superPrototypes.add(info.typeName());
303-
ignoredInterfaces.add(info.typeName());
304-
ignoredInterfaces.add(TypeName.builder(info.typeName())
305-
.className(info.typeName().className() + "Blueprint")
306-
.build());
307-
// also add all super interfaces of the prototype
308-
info.interfaceTypeInfo()
309-
.stream()
310-
.map(TypeInfo::typeName)
311-
.map(TypeName::genericTypeName)
312-
.forEach(ignoredInterfaces::add);
303+
304+
// we need to ignore ANY interface implemented by "info" and its super interfaces
305+
if (ignoredInterfaces.add(info.typeName().genericTypeName())) {
306+
ignoredInterfaces.add(TypeName.builder(info.typeName())
307+
.className(info.typeName().className() + "Blueprint")
308+
.build());
309+
ignoreAllInterfaces(ignoredInterfaces, info);
310+
}
313311
break;
314312
}
315313
}
@@ -319,6 +317,17 @@ private static void gatherExtends(TypeInfo typeInfo, Set<TypeName> extendList,
319317
}
320318
}
321319

320+
private static void ignoreAllInterfaces(Set<TypeName> ignoredInterfaces, TypeInfo info) {
321+
// also add all super interfaces of the prototype
322+
List<TypeInfo> superIfaces = info.interfaceTypeInfo();
323+
324+
for (TypeInfo superIface : superIfaces) {
325+
if (ignoredInterfaces.add(superIface.typeName().genericTypeName())) {
326+
ignoreAllInterfaces(ignoredInterfaces, superIface);
327+
}
328+
}
329+
}
330+
322331
@SuppressWarnings("checkstyle:ParameterNumber") // we need all of them
323332
private static void gatherBuilderProperties(ProcessingContext processingContext,
324333
TypeInfo typeInfo,

builder/processor/src/main/java/io/helidon/builder/processor/TypeHandlerMap.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ TypeName actualType() {
7777

7878
@Override
7979
Optional<String> generateFromConfig(PrototypeProperty.ConfiguredOption configured, FactoryMethods factoryMethods) {
80-
if (STRING_TYPE.equals(actualType)) {
81-
return Optional.of("config.get(\"" + configured.configKey() + "\").asMap().ifPresent(this::" + setterName() + ");");
82-
}
83-
8480
return Optional.of("config.get(\"" + configured.configKey() + "\").asNodeList().ifPresent(nodes -> nodes.forEach"
8581
+ "(node -> "
8682
+ name() + ".put(node.get(\"name\").asString().orElse(node.name()), node"

common/buffers/src/main/java/io/helidon/common/buffers/BufferData.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -210,7 +210,7 @@ static BufferData create(String stringValue) {
210210
* Read bytes from the input stream.
211211
* Reads at least 1 byte.
212212
* @param in input stream
213-
* @return number of bytes read
213+
* @return number of bytes read, -1 if the input stream is finished
214214
*/
215215
int readFrom(InputStream in);
216216

@@ -574,7 +574,7 @@ default BufferData copy() {
574574
/**
575575
* Number of bytes available for reading.
576576
*
577-
* @return available byte
577+
* @return available bytes
578578
*/
579579
int available();
580580

@@ -642,4 +642,16 @@ default void writeAscii(String text) {
642642
* @return byte at the index
643643
*/
644644
int get(int index);
645+
646+
/**
647+
* Read the content of this data as bytes.
648+
* This method always creates a new byte array.
649+
*
650+
* @return byte array with {@link #available()} bytes, may be empty
651+
*/
652+
default byte[] readBytes() {
653+
byte[] bytes = new byte[available()];
654+
read(bytes);
655+
return bytes;
656+
}
645657
}

common/http/src/main/java/io/helidon/common/http/HeadersImpl.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -163,11 +163,17 @@ public T remove(HeaderName name, Consumer<HeaderValue> removedConsumer) {
163163
@Override
164164
public T set(HeaderValue header) {
165165
HeaderName name = header.headerName();
166+
167+
HeaderValue usedHeader = header;
168+
if (header instanceof HeaderValueWriteable) {
169+
// we must create a new instance, as we risk modifying state of the provided header
170+
usedHeader = new HeaderValueCopy(header);
171+
}
166172
int index = name.index();
167173
if (index == -1) {
168-
customHeaders.put(name, header);
174+
customHeaders.put(name, usedHeader);
169175
} else {
170-
knownHeaders[index] = header;
176+
knownHeaders[index] = usedHeader;
171177
knownHeaderIndices.add(index);
172178
}
173179
return (T) this;

common/http/src/main/java/io/helidon/common/http/RequestedUriDiscoveryContext.java

Lines changed: 18 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.List;
20-
import java.util.Optional;
2120

2221
import io.helidon.common.config.Config;
2322
import io.helidon.common.configurable.AllowList;
@@ -289,7 +288,7 @@ public UriInfo uriInfo(String remoteAddress,
289288
String requestPath,
290289
ServerRequestHeaders headers,
291290
UriQuery query,
292-
boolean isSecure) {
291+
boolean isSecure) {
293292
String scheme = null;
294293
String authority = null;
295294
String host = null;
@@ -340,34 +339,28 @@ public UriInfo uriInfo(String remoteAddress,
340339
}
341340
}
342341

342+
UriInfo.Builder uriInfo = UriInfo.builder();
343+
343344
// now we must fill values that were not discovered (to have a valid URI information)
344345
if (host == null && authority == null) {
345346
authority = headers.first(Http.Header.HOST).orElse(null);
346347
}
347348

348-
if (path == null) {
349-
path = requestPath;
349+
uriInfo.path(path == null ? requestPath : path);
350+
uriInfo.host(localAddress); // this is the fallback
351+
if (authority != null) {
352+
uriInfo.authority(authority); // this is the second possibility
350353
}
351-
352-
if (host == null && authority != null) {
353-
Authority a;
354-
if (scheme == null) {
355-
a = Authority.create(authority);
356-
} else {
357-
a = Authority.create(scheme, authority);
358-
}
359-
if (a.host() != null) {
360-
host = a.host();
361-
}
362-
if (port == -1) {
363-
port = a.port();
364-
}
354+
if (host != null) {
355+
uriInfo.host(host); // and this one has priority
356+
}
357+
if (port != -1) {
358+
uriInfo.port(port);
365359
}
366360

367-
/*
368-
Discover final values to be used
369-
*/
370-
361+
/*
362+
Discover final values to be used
363+
*/
371364
if (scheme == null) {
372365
if (port == 80) {
373366
scheme = "http";
@@ -377,23 +370,10 @@ public UriInfo uriInfo(String remoteAddress,
377370
scheme = isSecure ? "https" : "http";
378371
}
379372
}
373+
uriInfo.scheme(scheme);
374+
uriInfo.query(query);
380375

381-
if (host == null) {
382-
host = localAddress;
383-
}
384-
385-
// we may still have -1, if port was not explicitly defined by a header - use default port of protocol
386-
if (port == -1) {
387-
if ("https".equals(scheme)) {
388-
port = 443;
389-
} else {
390-
port = 80;
391-
}
392-
}
393-
if (query == null || query.isEmpty()) {
394-
query = null;
395-
}
396-
return new UriInfo(scheme, host, port, path, Optional.ofNullable(query));
376+
return uriInfo.build();
397377
}
398378

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

470-
private record Authority(String host, int port) {
471-
static Authority create(String hostHeader) {
472-
int colon = hostHeader.indexOf(':');
473-
if (colon == -1) {
474-
// we do not know the protocol, and there is no port defined
475-
return new Authority(hostHeader, -1);
476-
}
477-
String hostString = hostHeader.substring(0, colon);
478-
String portString = hostHeader.substring(colon + 1);
479-
return new Authority(hostString, Integer.parseInt(portString));
480-
}
481-
static Authority create(String scheme, String hostHeader) {
482-
int colon = hostHeader.indexOf(':');
483-
if (colon == -1) {
484-
// define port by protocol
485-
return new Authority(hostHeader, "https".equals(scheme) ? 443 : 80);
486-
}
487-
String hostString = hostHeader.substring(0, colon);
488-
String portString = hostHeader.substring(colon + 1);
489-
return new Authority(hostString, Integer.parseInt(portString));
490-
}
491-
}
492-
493450
private record ForwardedDiscovery(String authority, String scheme) {}
494451
private record XForwardedDiscovery(String scheme, String host, int port, String path) {}
495452

0 commit comments

Comments
 (0)