Skip to content

Commit 1a2ab6e

Browse files
committed
fix: Force HTTP/1.1 on non Hetzner hosts
- Fixes a test having issues likely due to HTTP/2
1 parent ea06ea0 commit 1a2ab6e

File tree

3 files changed

+43
-10
lines changed

3 files changed

+43
-10
lines changed

src/main/java/dev/tomr/hcloud/HetznerCloud.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,12 @@ public boolean hasApiKey() {
115115
public Server getServer(Integer id) {
116116
return serviceManager.getServerService().getServer(id);
117117
}
118+
119+
/**
120+
* Returns the HETZNER_CLOUD_HOST constant
121+
* @return String with the hetzner cloud host
122+
*/
123+
public static String getHetznerCloudHost() {
124+
return HETZNER_CLOUD_HOST;
125+
}
118126
}

src/main/java/dev/tomr/hcloud/http/HetznerCloudHttpClient.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,15 @@ public <T extends HetznerJsonObject> T sendHttpRequest(Class<T> clazz, String en
8888
}
8989

9090
private HttpRequest createHttpRequest(String uri, RequestVerb requestVerb, String apiKey, String body) {
91+
// wiremock seems to have problems with using HTTP/2 sometimes (seems like a bug in the http client) - therefore if we know it's Hetzner,
92+
// we'll enforce HTTP/2 - otherwise HTTP/1.1
93+
HttpClient.Version version = HttpClient.Version.HTTP_1_1;
94+
if (HetznerCloud.getInstance().getHttpDetails().get(0).equals(HetznerCloud.getHetznerCloudHost())) {
95+
version = HttpClient.Version.HTTP_2;
96+
}
9197
HttpRequest.Builder builder = HttpRequest.newBuilder()
9298
.uri(URI.create(uri))
99+
.version(version)
93100
.header("Authorization", format("Bearer %s", apiKey))
94101
.header("Content-Type", "application/json");
95102
builder = switch (requestVerb) {

src/test/java/dev/tomr/hcloud/component/http/HttpClientComponentTest.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package dev.tomr.hcloud.component.http;
22

3+
import com.fasterxml.jackson.core.JsonProcessingException;
34
import com.fasterxml.jackson.databind.ObjectMapper;
45
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
56
import com.github.tomakehurst.wiremock.common.Notifier;
67
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
78
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
89
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
10+
import dev.tomr.hcloud.HetznerCloud;
911
import dev.tomr.hcloud.http.HetznerCloudHttpClient;
1012
import dev.tomr.hcloud.http.RequestVerb;
1113
import dev.tomr.hcloud.http.exception.HetznerApiException;
@@ -14,12 +16,15 @@
1416
import org.junit.jupiter.api.extension.RegisterExtension;
1517
import org.junit.jupiter.params.ParameterizedTest;
1618
import org.junit.jupiter.params.provider.ValueSource;
19+
import org.mockito.MockedStatic;
1720

1821
import java.io.IOException;
22+
import java.util.List;
1923

2024
import static com.github.tomakehurst.wiremock.client.WireMock.*;
2125
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
2226
import static org.junit.jupiter.api.Assertions.*;
27+
import static org.mockito.Mockito.*;
2328

2429
public class HttpClientComponentTest {
2530

@@ -99,15 +104,7 @@ void testHttpClientThrowsBadResponseGracefully() {
99104

100105
HetznerApiException hetznerApiException = assertThrows(
101106
HetznerApiException.class,
102-
() -> {
103-
try {
104-
client.sendHttpRequest(TestModel.class, HOST + "badResponse", RequestVerb.GET, "");
105-
} catch (Exception e) {
106-
System.out.println(e.getMessage());
107-
e.printStackTrace();
108-
throw e;
109-
}
110-
}
107+
() -> client.sendHttpRequest(TestModel.class, HOST + "badResponse", RequestVerb.GET, "")
111108
);
112109

113110
assertEquals("HetznerErrorResponse [code=uniqueness_error, message=SSH key with the same fingerprint already exists]", hetznerApiException.getMessage());
@@ -124,12 +121,33 @@ void testHttpClientThrowsHetznerApiExceptionWhenBadJsonResponse() {
124121

125122
@Test
126123
@DisplayName("HTTP Client handles 204 no content correctly")
127-
void httpClientHandles204NoContent() throws IOException, InterruptedException, IllegalAccessException {
124+
void httpClientHandles204NoContent() {
128125
// this test is needed because 204 does not support handling a body
129126
// todo refactor how we handle HTTP Status codes we **know** will never supply a body, i.e. 204 - this implementation leaves a lot to be desired
130127
wireMockExtension.stubFor(get("/test").willReturn(aResponse().withStatus(204)));
131128
HetznerCloudHttpClient client = new HetznerCloudHttpClient();
132129

133130
assertDoesNotThrow(() -> client.sendHttpRequest(TestModel.class, HOST + "test", RequestVerb.GET, ""));
134131
}
132+
133+
@Test
134+
@DisplayName("Request uses HTTP/2 when targeting Hetzner, instead of HTTP/1.1 by default")
135+
void requestUsesHttp2WhenTargetingHetznerHost() throws IOException, InterruptedException, IllegalAccessException {
136+
wireMockExtension.stubFor(get("/test").willReturn(ok(objectMapper.writeValueAsString(new TestModel(1, 1, "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto")))));
137+
HetznerCloud hetznerCloud = mock(HetznerCloud.class);
138+
try (MockedStatic<HetznerCloud> hetznerCloudMockedStatic = mockStatic(HetznerCloud.class)) {
139+
hetznerCloudMockedStatic.when(HetznerCloud::getHetznerCloudHost).thenReturn(HOST);
140+
hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud);
141+
when(hetznerCloud.getHttpDetails()).thenReturn(List.of(HOST, ""));
142+
143+
HetznerCloudHttpClient client = new HetznerCloudHttpClient();
144+
145+
TestModel testModel = client.sendHttpRequest(TestModel.class, HOST + "test", RequestVerb.GET, "");
146+
147+
assertEquals(1, testModel.getId());
148+
assertEquals(1, testModel.getUserId());
149+
assertEquals("sunt aut facere repellat provident occaecati excepturi optio reprehenderit", testModel.getTitle());
150+
assertEquals("quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto", testModel.getBody());
151+
}
152+
}
135153
}

0 commit comments

Comments
 (0)