Skip to content

Commit

Permalink
CAMEL-21461: Add features to Platform Http SB Starter
Browse files Browse the repository at this point in the history
Certification Tests + Minor Fixes

Add failing tests

Add ReaderCache Converter test

fix streaming large file test

header mapping is already done in DefaultHttpBinding

fix streaming

CSB-5889: Let's cache the body so that is can be read multiple times

CSB-5889: regen

Disable not implemented feature tests
  • Loading branch information
Croway committed Nov 19, 2024
1 parent c0e9b6b commit 50d6f87
Show file tree
Hide file tree
Showing 23 changed files with 1,872 additions and 24 deletions.
39 changes: 39 additions & 0 deletions components-starter/camel-platform-http-starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@
<version>${rest-assured-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-hazelcast</artifactId>
<version>3.3.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>${hazelcast-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
Expand All @@ -65,6 +83,27 @@
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-jackson-starter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-http-starter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock.integrations</groupId>
<artifactId>wiremock-spring-boot</artifactId>
<version>3.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.camel.springboot</groupId>
<artifactId>camel-jsonpath-starter</artifactId>
<scope>test</scope>
</dependency>
<!--START OF GENERATED CODE-->
<dependency>
<groupId>org.apache.camel.springboot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@
"name": "camel.component.platform-http.autowired-enabled",
"type": "java.lang.Boolean",
"description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc.",
"sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration",
"defaultValue": true
"sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration"
},
{
"name": "camel.component.platform-http.bridge-error-handler",
"type": "java.lang.Boolean",
"description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored.",
"sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration",
"defaultValue": false
"sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration"
},
{
"name": "camel.component.platform-http.customizer.enabled",
Expand All @@ -48,8 +46,7 @@
"name": "camel.component.platform-http.handle-write-response-error",
"type": "java.lang.Boolean",
"description": "When Camel is complete processing the message, and the HTTP server is writing response. This option controls whether Camel should catch any failure during writing response and store this on the Exchange, which allows onCompletion\/UnitOfWork to regard the Exchange as failed and have access to the caused exception from the HTTP server.",
"sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration",
"defaultValue": false
"sourceType": "org.apache.camel.component.platform.http.springboot.PlatformHttpComponentConfiguration"
},
{
"name": "camel.component.platform-http.header-filter-strategy",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,19 @@ private RequestMappingInfo asRequestMappingInfo(HttpEndpointModel model) {
}
}

RequestMappingInfo info = RequestMappingInfo.paths(model.getUri())
.methods(methods.toArray(new RequestMethod[0])).options(this.getBuilderConfiguration()).build();
return info;
RequestMappingInfo.Builder info = RequestMappingInfo
.paths(model.getUri())
.methods(methods.toArray(new RequestMethod[0]))
.options(this.getBuilderConfiguration());

if (model.getConsumes() != null) {
info.consumes(model.getConsumes().split(","));
}
if (model.getProduces() != null) {
info.produces(model.getProduces().split(","));
}

return info.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import jakarta.servlet.http.HttpServletResponse;
import org.apache.camel.Exchange;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.StreamCache;
import org.apache.camel.http.common.HttpBinding;
import org.apache.camel.support.DefaultMessage;
import org.apache.camel.util.ObjectHelper;
Expand Down Expand Up @@ -60,7 +61,12 @@ public void init(Exchange exchange, HttpBinding binding, HttpServletRequest requ
if (flag != null && flag) {
this.setHeader("CamelSkipWwwFormUrlEncoding", Boolean.TRUE);
}

// we need to favor using stream cache so the body can be re-read later when routing the message
StreamCache newBody = camelContext.getTypeConverter().tryConvertTo(StreamCache.class,
exchange, getBody());
if (newBody != null) {
setBody(newBody);
}
binding.readRequest(request, this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,35 @@
*/
package org.apache.camel.component.platform.http.springboot;

import jakarta.activation.DataHandler;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.camel.Message;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.camel.*;
import org.apache.camel.attachment.AttachmentMessage;
import org.apache.camel.attachment.CamelFileDataSource;
import org.apache.camel.component.platform.http.PlatformHttpEndpoint;
import org.apache.camel.converter.stream.CachedOutputStream;
import org.apache.camel.http.base.HttpHelper;
import org.apache.camel.http.common.DefaultHttpBinding;
import org.apache.camel.support.ExchangeHelper;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;

import java.io.*;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;

public class SpringBootPlatformHttpBinding extends DefaultHttpBinding {
private static final Logger LOG = LoggerFactory.getLogger(SpringBootPlatformHttpBinding.class);

protected void populateRequestParameters(HttpServletRequest request, Message message) {
super.populateRequestParameters(request, message);
Expand All @@ -47,4 +69,150 @@ private boolean useRestMatching(String path) {
return path.indexOf('{') > -1;
}

@Override
protected void populateAttachments(HttpServletRequest request, Message message) {
// check if there is multipart files, if so will put it into DataHandler
if (request instanceof MultipartHttpServletRequest multipartHttpServletRequest) {
File tmpFolder = (File) request.getServletContext().getAttribute(ServletContext.TEMPDIR);
multipartHttpServletRequest.getFileMap().forEach((name, multipartFile) -> {
try {
Path uploadedTmpFile = Paths.get(tmpFolder.getPath(), name);
multipartFile.transferTo(uploadedTmpFile);

if (name != null) {
name = name.replaceAll("[\n\r\t]", "_");
}

boolean accepted = true;

if (getFileNameExtWhitelist() != null) {
String ext = FileUtil.onlyExt(name);
if (ext != null) {
ext = ext.toLowerCase(Locale.US);
if (!getFileNameExtWhitelist().equals("*") && !getFileNameExtWhitelist().contains(ext)) {
accepted = false;
}
}
}

if (accepted) {
AttachmentMessage am = message.getExchange().getMessage(AttachmentMessage.class);
am.addAttachment(name, new DataHandler(new CamelFileDataSource(uploadedTmpFile.toFile(), name)));
} else {
LOG.debug(
"Cannot add file as attachment: {} because the file is not accepted according to fileNameExtWhitelist: {}",
name, getFileNameExtWhitelist());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}

public Object parseBody(HttpServletRequest request, Message message) throws IOException {
if (request instanceof StandardMultipartHttpServletRequest) {
return null;
}

return super.parseBody(request, message);
}

protected void doWriteDirectResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
String contentType = (String)message.getHeader("Content-Type", String.class);
if ("application/x-java-serialized-object".equals(contentType)) {
if (!isAllowJavaSerializedObject() && !this.isTransferException()) {
throw new RuntimeCamelException("Content-type application/x-java-serialized-object is not allowed");
} else {
try {
Object object = message.getMandatoryBody(Serializable.class);
org.apache.camel.http.common.HttpHelper.writeObjectToServletResponse(response, object);
} catch (InvalidPayloadException var19) {
InvalidPayloadException e = var19;
throw new IOException(e);
}
}
} else {
InputStream is = null;
if (this.checkChunked(message, exchange)) {
is = message.getBody(InputStream.class);
} else if (!this.isText(contentType)) {
is = exchange.getContext().getTypeConverter().tryConvertTo(InputStream.class, message.getBody());
}

int len;
if (is != null) {
ServletOutputStream os = response.getOutputStream();
if (!this.checkChunked(message, exchange)) {
CachedOutputStream stream = new CachedOutputStream(exchange);

try {
len = this.copyStream(is, stream, response.getBufferSize());
response.setContentLength(len);
OutputStream current = stream.getCurrentStream();
if (current instanceof ByteArrayOutputStream) {
ByteArrayOutputStream bos = (ByteArrayOutputStream)current;
if (LOG.isDebugEnabled()) {
LOG.debug("Streaming (direct) response in non-chunked mode with content-length {}", len);
}

bos.writeTo(os);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Streaming response in non-chunked mode with content-length {} and buffer size: {}", len, len);
}

this.copyStream(stream.getInputStream(), os, len);
}
} finally {
IOHelper.close(new Closeable[]{is, os});
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Streaming response in chunked mode with buffer size {}", response.getBufferSize());
}

this.copyStream(is, os, response.getBufferSize());
}
} else {
Object body = message.getBody();
if (body instanceof String) {
String data = message.getBody(String.class);

if (data != null) {
String charset = ExchangeHelper.getCharsetName(exchange, true);
len = data.getBytes(charset).length;
response.setCharacterEncoding(charset);
response.setContentLength(len);
if (LOG.isDebugEnabled()) {
LOG.debug("Writing response in non-chunked mode as plain text with content-length {} and buffer size: {}", len, response.getBufferSize());
}

try {
response.getWriter().print(data);
} finally {
response.getWriter().flush();
}
}
} else if (body instanceof InputStream) {
InputStream bodyIS = message.getBody(InputStream.class);
bodyIS.transferTo(response.getOutputStream());
} else {
final TypeConverter tc = exchange.getContext().getTypeConverter();
// Try to convert to ByteBuffer for performance reason
final ByteBuffer bb = tc.tryConvertTo(ByteBuffer.class, exchange, body);
if (bb != null) {
response.getOutputStream().write(bb.array());
} else {
try {
final InputStream bodyIS = tc.mandatoryConvertTo(InputStream.class, exchange, body);
bodyIS.transferTo(response.getOutputStream());
} catch (NoTypeConversionAvailableException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
Expand All @@ -32,14 +34,11 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class})
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
@CamelSpringBootTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.apache.camel.component.platform.http.springboot;

import java.util.concurrent.TimeUnit;
import org.apache.camel.CamelContext;
import org.apache.camel.ServiceStatus;
import org.assertj.core.api.Assertions;
Expand All @@ -25,6 +24,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;

import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.assertEquals;

abstract class PlatformHttpBase {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import org.apache.camel.util.IOHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean;
Expand All @@ -42,7 +44,7 @@
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootApplication
@EnableAutoConfiguration(exclude = {OAuth2ClientAutoConfiguration.class, SecurityAutoConfiguration.class})
@DirtiesContext
@CamelSpringBootTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { CamelAutoConfiguration.class,
Expand Down
Loading

0 comments on commit 50d6f87

Please sign in to comment.