Skip to content

Commit 460cd2e

Browse files
Add client guidance for HTTP interfaces
1 parent e06e4e2 commit 460cd2e

File tree

4 files changed

+275
-33
lines changed

4 files changed

+275
-33
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "documentation",
3+
"description": "Added a section to discuss recommendations for implementing Smithy clients. Currently this section only includes information about HTTP interfaces, but it will expand over time to cover more topics related to implementing clients.",
4+
"pull_requests": [
5+
"[#2868](https://github.com/smithy-lang/smithy/pull/2868)"
6+
]
7+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# HTTP
2+
3+
HTTP is the most common application protocol used by Smithy clients. This guide
4+
provides advice on how to integrate and expose HTTP clients.
5+
6+
## Configuration
7+
8+
Smithy clients should allow their users to configure the HTTP client that the
9+
Smithy client uses to send requests. Users may want to change the client used to
10+
something that has different performance characteristics or support for features
11+
that they need.
12+
13+
## HTTP interfaces
14+
15+
Smithy clients should provide interfaces for HTTP clients that standardize how
16+
the Smithy client interacts with the HTTP client, allowing any HTTP client to be
17+
used as long as it implements the interface.
18+
19+
### Clients
20+
21+
It is recommended to make HTTP clients implementations of
22+
[`ClientTransport`](#transport-clients).
23+
24+
```java
25+
public interface HttpClient implements ClientTransport<HttpRequest, HttpResponse> {
26+
HttpResponse send(Context context, HttpRequest request);
27+
}
28+
```
29+
30+
#### Context
31+
32+
HTTP clients don't have many common context parameters, but they should check
33+
the context for a request timeout setting and use it if it's present.
34+
35+
```java
36+
/**
37+
* This utility class holds shared context key definitions that are useful
38+
* for HTTP implementations.
39+
*/
40+
public final class HttpContext {
41+
public static final Context.Key<Duration> HTTP_REQUEST_TIMEOUT = Context.key("HTTP.RequestTimeout");
42+
43+
// This is a utility class that is not intended to be constructed, so it
44+
// has a private constructor.
45+
private HttpContext() {}
46+
}
47+
```
48+
49+
### Requests and Responses
50+
51+
{rfc}`9110` discusses HTTP requests and responses collectively as "messages",
52+
and it can be useful to encode their shared features in a shared interface.
53+
54+
```java
55+
public interface HttpMessage {
56+
/**
57+
* Get the headers of the message.
58+
*
59+
* @return headers.
60+
*/
61+
HttpFields headers();
62+
63+
/**
64+
* Get the body of the message, or null.
65+
*
66+
* @return the message body or null.
67+
*/
68+
DataStream body();
69+
}
70+
```
71+
72+
Requests introduce the `method` and `uri` properties.
73+
74+
```java
75+
public interface HttpRequest extends HttpMessage {
76+
/**
77+
* Get the method of the request.
78+
*
79+
* @return the method.
80+
*/
81+
String method();
82+
83+
/**
84+
* Get the URI of the request.
85+
*
86+
* @return the request URI.
87+
*/
88+
URI uri();
89+
}
90+
```
91+
92+
Responses introduce a status code.
93+
94+
```java
95+
public interface HttpResponse extends HttpMessage {
96+
/**
97+
* Get the status code of the response.
98+
*
99+
* @return the status code.
100+
*/
101+
int statusCode();
102+
}
103+
```
104+
105+
### Fields
106+
107+
Most users who have interacted with HTTP directly are familiar with the concept
108+
of headers. Headers were originally introduced in HTTP/1.0 and, since then, the
109+
concept of key/value pairs has expanded to include trailers and other arbitrary
110+
metadata. As of {rfc}`9110`, these key/value pairs are exclusively referred to
111+
as {rfc}`fields <9110#section-5>`.
112+
113+
When designing HTTP interfaces for Smithy clients, be careful to understand
114+
field semantics. In particular, it is important to understand that field keys
115+
are case-insensitive and may appear more than once in an HTTP message. Since
116+
field keys may appear more than once, it is recommended that they are
117+
represented as an iterable collection of pairs or as a map whose value type is a
118+
list. This allows protocol implementations to safely handle joining and
119+
splitting.
120+
121+
It is recommended to have utilities to convert fields to and from maps. Fields
122+
are often conceptualized as maps, so providing these utilities allows users to
123+
access fields in a more comfortable way without sacrificing correctness.
124+
125+
```java
126+
public interface HttpFields extends Iterable<Map.Entry<String, List<String>>> {
127+
/**
128+
* Create an HttpFields instance from a map.
129+
*
130+
* @param fields Field map to use as a data source.
131+
* @return the created fields.
132+
*/
133+
static HttpFields of(Map<String, List<String>> fields) {
134+
// This constructs a theoretical default implementation of the
135+
// HttpFields interface that creates an unmodifiable copy of the given
136+
// map.
137+
return fields.isEmpty() ? UnmodifiableHttpFields.EMPTY : new UnmodifiableHttpFields(fields);
138+
}
139+
140+
/**
141+
* Convert the HttpFields to a map.
142+
*
143+
* @return the fields as a map.
144+
*/
145+
Map<String, List<String>> toMap();
146+
147+
/**
148+
* Check if the given field is case-insensitively present.
149+
*
150+
* @param name Name of the field to check.
151+
* @return true if the field is present.
152+
*/
153+
default boolean containsField(String name) {
154+
return !getAllValues(name).isEmpty();
155+
}
156+
157+
/**
158+
* Get the first field value of a specific field by case-insensitive name.
159+
*
160+
* Smithy clients know whether a given field should have a single value or
161+
* a list value. This helper method simplifies usage for fields with a
162+
* single value.
163+
*
164+
* @param name Name of the field to get.
165+
* @return the matching field value, or null if not found.
166+
*/
167+
default String getFirstValue(String name) {
168+
var list = getAllValues(name);
169+
return list.isEmpty() ? null : list.get(0);
170+
}
171+
172+
/**
173+
* Get the values of a specific field by case-insensitive name.
174+
*
175+
* @param name Name of the field to get the values of.
176+
* @return the values of the field, or an empty list.
177+
*/
178+
List<String> getAllValues(String name);
179+
}
180+
```
181+
182+
#### Implementation recommendations
183+
184+
It is not recommended to automatically attempt to join values for a given field
185+
key at the HTTP layer. {rfc}`9110#section-5` allows field values to be joined
186+
with a comma, but doing so automatically can introduce data corruption if one of
187+
the field values already includes a comma. {rfc}`Section 5.6 <9110#section-5.6>`
188+
includes productions that can help to handle those edge cases, but whether they
189+
are used or not is up to the protocol definition.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Application Protocols
2+
3+
This section provides guidance on how to implement and integrate different
4+
application protocols into a client.
5+
6+
Application protocols define how operations are transmitted over a network. The
7+
most commonly used application protocol by Smithy clients is HTTP, but other
8+
protocols like MQTT may also be used by Smithy clients and servers. When
9+
designing a client, be careful to not couple any components to a particular
10+
application protocol unless they interact explicitly with that protocol. For
11+
example, an HTTP request serializer inherently needs to be coupled to HTTP, but
12+
a JSON serializer does not.
13+
14+
(transport-clients)=
15+
## Transport clients
16+
17+
Smithy clients and services have a common access pattern regardless of what
18+
application protocol is being used: a client sends requests to a server and
19+
receives responses. This can be represented by a simple interface:
20+
21+
```java
22+
public interface ClientTransport<RequestT, ResponseT> {
23+
ResponseT send(Context context, RequestT request);
24+
}
25+
```
26+
27+
In addition to the request, it is recommended to introduce a context parameter
28+
to the `send` method to allow the client to be configured for each request. It
29+
is recommended to make this a generic context object rather than a type with
30+
fixed properties. Leaving it unrestricted allows context to be passed into
31+
custom `ClientTransport` implementations that may not be relevant to other
32+
implementations.
33+
34+
## Navigation
35+
36+
```{toctree}
37+
:maxdepth: 1
38+
39+
http
40+
```

docs/source-2.0/guides/client-guidance/index.md

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,45 +11,51 @@ generator, see
1111

1212
## Goals of this guide
1313

14-
- Give advice on how to design client components.
14+
- Provide guidance on how to design client components.
1515
- Provide guidance on how to avoid coupling components to particular transport
16-
protocols or to AWS-specific features.
16+
protocols or to specific features.
1717
- Provide guidance on how to make clients extensible.
1818

1919
## Non-goals of this guide
2020

2121
- Provide guidance on how to design and implement code generators.
22-
- Force specific implementation details. This guide is non-normative, you are
23-
free to deviate from its advice.
22+
- Force specific implementation details. This guide is non-normative, feel free
23+
to deviate from its advice.
2424

2525
## Tenets for Smithy clients
2626

27-
These are the tenets of Smithy clients (unless you know better ones):
28-
29-
1. **Smithy implementations adhere to the spec**. The Smithy spec and model
30-
are the contract between clients, servers, and other implementations.
31-
A Smithy client written in any programming language should be able to
32-
connect to a Smithy server written in any programming language without
33-
either having to care about the programming language used to implement
34-
the other.
35-
2. **Smithy clients are familiar to developers**. Language idioms
36-
and developer experience factor in to how developers and companies
37-
choose between Smithy and alternatives.
38-
3. **Components, not monoliths**. We write modular components that
39-
developers can compose together to meet their requirements. Our
40-
components have clear boundaries: a client that uses an AWS protocol is
41-
not required to use AWS credentials.
42-
4. **Our code is maintainable because we limit public interfaces**. We
43-
limit the dependencies we take on. We don't expose overly open
44-
interfaces that hinder our ability to evolve the code base.
45-
5. **No implementation stands alone**. Test cases, protocol tests, code
46-
fixes, and missing abstractions have a greater impact if every Smithy
47-
implementation can use them rather than just a single implementation.
48-
6. **Service teams don't need to know the details of every client
49-
that exists or will ever exist**. When modeling a service,
50-
service teams only need to consider if the model is a valid Smithy
51-
model; the constraints of any particular programming language should
52-
not be a concern when modeling a service. Smithy is meant to work
53-
with any number of languages, and it is an untenable task to attempt
54-
to bubble up every constraint, reserved word, or other limitation to
55-
modelers.
27+
Smithy clients should follow these tenets:
28+
29+
1. **Smithy implementations adhere to the spec**. The Smithy spec and model are
30+
the contract between clients, servers, and other implementations. A Smithy
31+
client written in any programming language should be able to connect to a
32+
Smithy server written in any programming language without either having to
33+
care about the programming language used to implement the other.
34+
2. **Smithy clients should be familiar to developers**. Language idioms and
35+
developer experience factor into how developers and companies choose between
36+
Smithy and alternatives.
37+
3. **Components are preferred over monoliths**. Components should be modular and
38+
composable. They should have clear boundaries: a client that uses an AWS
39+
protocol is not required to use AWS credentials, for example.
40+
4. **Smithy client code should prioritize maintainability by limiting public
41+
interfaces**. Smithy clients should limit the dependencies they take on. They
42+
shouldn't expose overly open interfaces that hinder the ability to evolve the
43+
code base.
44+
5. **No implementation stands alone**. Test cases, protocol tests, code fixes,
45+
and missing abstractions have a greater impact if every Smithy implementation
46+
can use them rather than just a single implementation.
47+
6. **Service teams don't need to know the details of every client that exists or
48+
will ever exist**. When modeling a service, service teams only need to
49+
consider if the model is a valid Smithy model; the constraints of any
50+
particular programming language should not be a concern when modeling a
51+
service. Smithy is meant to work with any number of languages, and it is an
52+
untenable task to attempt to bubble up every constraint, reserved word, or
53+
other limitation to modelers.
54+
55+
## Navigation
56+
57+
```{toctree}
58+
:maxdepth: 1
59+
60+
application-protocols/index
61+
```

0 commit comments

Comments
 (0)