Skip to content

Instantly share code, notes, and snippets.

@boly38
Last active November 21, 2024 14:28
Show Gist options
  • Save boly38/94fddf8ec9acda887cfa6f1b5b07ae7e to your computer and use it in GitHub Desktop.
Save boly38/94fddf8ec9acda887cfa6f1b5b07ae7e to your computer and use it in GitHub Desktop.
package com.example.company.interceptors;
import java.io.IOException;
import java.net.URI;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
/**
* RestTemplate, the '+' sign is not encoded to '%2B' anymore (changes by SPR-14828).
* <a href="https://stackoverflow.com/questions/54294843/plus-sign-not-encoded-with-resttemplate-using-string-url-but-interpreted">SO + interpreted as space</a>
* RFC3986 doesn't list '+' as a reserved character,
* but it is still interpreted as a ' ' (space) when received in a Spring Boot endpoint
*/
@Slf4j
public class PlusEncoderInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
return execution.execute(new HttpRequestWrapper(request) {
@Override
public @NonNull URI getURI() {
URI u = super.getURI();
String rawQuery = u.getRawQuery();
String strictlyEscapedQuery = StringUtils
.replace(rawQuery, "+", "%2B");
if (log.isDebugEnabled() && !strictlyEscapedQuery.equals(rawQuery)) {
log.debug("Plus intercept> {} => {}", rawQuery, strictlyEscapedQuery);
}
return UriComponentsBuilder.fromUri(u)
.replaceQuery(strictlyEscapedQuery)
.build(true).toUri();
}
}, body);
}
}
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.net.URI;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;
class PlusEncoderInterceptorTest {
private PlusEncoderInterceptor interceptor;
private HttpRequest request;
private ClientHttpRequestExecution execution;
@BeforeEach
public void setUp() {
interceptor = new PlusEncoderInterceptor();
request = mock(HttpRequest.class);
execution = mock(ClientHttpRequestExecution.class);
}
@Test
void testPlusCharacterEncoding() throws Exception {
String originalQuery = "param1=value1+value2";
URI originalUri = URI.create("http://example.com/api?" + originalQuery);
when(request.getURI()).thenReturn(originalUri);
ClientHttpResponse response = mock(ClientHttpResponse.class);
when(execution.execute(any(), any())).thenReturn(response);
// WHEN
interceptor.intercept(request, new byte[0], execution);
ArgumentCaptor<HttpRequest> requestCaptor = ArgumentCaptor.forClass(HttpRequest.class);
verify(execution).execute(requestCaptor.capture(), any());
URI capturedUri = requestCaptor.getValue().getURI();
String capturedQuery = capturedUri.getRawQuery();
// check '+' has been replaced by '%2B'
assertEquals("param1=value1%2Bvalue2", capturedQuery);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment