I'm creating a simple AWS Lambda Function in Java that creates and returns a PDF. The function is invoked by an API Gateway. The input is a simple POJO class, but the output should be an OutputStream for the file.
For the input, I've tried creating a POJO class and just using the APIGatewayProxyRequestEvent and either works fine. Below is a simple example I used that takes in a input and prints back the query string parameters.
public class LambdaFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
#Override
public APIGatewayProxyResponseEvent handleRequest( APIGatewayProxyRequestEvent input, Context context ) {
return new APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withHeaders(Collections.emptyMap())
.withBody("{\"input\":\"" + input.getQueryStringParameters() + "\"}");
}
}
That works fine, but now I need to alter it to use an OutputStream as the the output. How can this be done? I see that I can use the RequestStreamHandler and AWS has some documentation on implementing this. However, that would force my input to be an InputStream, which I'm not sure how that would work with the API Gateway.
How can I serve this PDF back to the client requesting it?
Remember that the POJO method of the Lambda handler is a convenience only. Ultimately, you could do this yourself and use the InputStream/OutputStream Lambda pattern. Something like:
public void handleRequest(InputStream inputStream,
OutputStream outputStream,
Context context) throws IOException {
String inputString = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining("\n"));
ObjectMapper objectMapper = new ObjectMapper();
APIGatewayProxyRequestEvent request = objectMapper.readValue(inputString, APIGatewayProxyRequestEvent.class);
// do your thing, generate a PDF
byte[] thePDF = ...
// create headers
Map<String, String> headers = new HashMap<>();
headers.put("Content-type", "application/pdf");
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent().
.withStatusCode(200)
.withHeaders(headers)
.withBody(Base64.Encoder.encode(thePDF))
.withIsBase64Encoded(Boolean.TRUE);
outputStream.write(objectMapper.writeValueAsString(response)
.getBytes(StandardCharsets.UTF_8));
}
However, I'm not convinced that this is really any better. If you want to return just the PDF without the APIGatewayProxyResponseEvent you can but now you'll have to update API Gateway to correctly send the Content-Type header.
Related
I'm using JAX-RS and within the WriterInterceptor, I need to access some information contained in the original request.
As an example, consider the below request body.
{
"ClientId": "MY_CLIENT_ID",
"UserId": "MY_USER_ID",
"AccountId": "MY_ACCOUNT_ID",
"Scope" : "MY_SCOPES",
}
Within my WriteInterceptor, I need to read Client ID and User ID from the request and add those values to the response.
I am currently working on a ReadInterceptor implementation for this. I initially assumed there is a way to put parameters to ReaderInterceptorContext and then read it somehow from the WriterInterceptorContext. But It seems there is no way to do that. ( Please correct me if I'm wrong).
So, now I'm trying to use a concurrent hashmap to store these parameters in the ReaderInterceptor and retrieve it in the WriteInterceptor. I need a unique key to create the correlation between request and response. Is it ok to use the thread ID for this?
Please point me if there is a better approach to resolve this problem
I resolved this problem by adding a container response filter which can add a header to the response. Read interceptor reads required parameters from the request and set those as context properties.
#Override
public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext)
throws IOException, WebApplicationException {
InputStream is = readerInterceptorContext.getInputStream();
String requestBody = new Scanner(is, StandardCharsets.UTF_8.name()).useDelimiter("\\A").next();
JSONObject request = new JSONObject(requestBody);
//Adding the stream back to the context object
readerInterceptorContext.setInputStream(new ByteArrayInputStream(requestBody.getBytes()));
//Adding properties to read in filter
readerInterceptorContext.setProperty("ClientId", request.get("ClientId"));
readerInterceptorContext.setProperty("UserId","UserId"));
return readerInterceptorContext.proceed();
}
These properties are then read inside the container response filter and added as a response header.
#Override
public void filter(ContainerRequestContext containerReqContext, ContainerResponseContext containerResponseContext) {
//Adding temporary headers to read in WriterInterceptor
containerResponseContext.getHeaders().add(
"ClientId", containerReqContext.getProperty("ClientId"));
containerResponseContext.getHeaders().add(
"UserId", containerReqContext.getProperty("UserId"));
}
Existing writer interceptor read these headers, add those to JWT and then remove as header values. I did a POC for this and it is working as expected
#Override
public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) throws IOException {
OutputStream outputStream = writerInterceptorContext.getOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writerInterceptorContext.setOutputStream(baos);
String clientId = writerInterceptorContext.getHeaders().getFirst("ClientId").toString();
String user = writerInterceptorContext.getHeaders().getFirst("UserId").toString();
}
I need to log the rest assured traffic - Doing that by using: when().post().then().log().all().extract().response();
Now that puts the rest messages in std out, but I want those messages to be logged in cucumber report in the AfterAll() call via a scenario.write().
How do I save the rest assured logs into a variable and pass that to scenario.write()?
You can use RequestLoggingFilter and ResponseLoggingFilter classes. Provide configured PrintStream to their constructor and add filters to request specification. It can look this way:
OutputStream outputStream = null; //use your OutputStream that will write where you need it
PrintStream printStream = new PrintStream(outputStream, true);
RequestLoggingFilter requestLoggingFilter = new RequestLoggingFilter(printStream);
ResponseLoggingFilter responseLoggingFilter = new ResponseLoggingFilter(printStream);
RestAssured.given()
.filters(requestLoggingFilter, responseLoggingFilter)
.when()
.get("/api");
Or you can also implement Filter interface to use your own.
Here's an example for logging both request and response:
public class CustomLogFilter implements Filter {
Logger log = LoggerFactory.getLogger(CustomLogFilter.class);
#Override
public Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec, FilterContext ctx) {
Response response = ctx.next(requestSpec, responseSpec);
StringBuilder requestBuilder = new StringBuilder();
requestBuilder.append(requestSpec.getMethod());
requestBuilder.append("\n");
requestBuilder.append(requestSpec.getURI());
requestBuilder.append("\n");
requestBuilder.append("*************");
log.info(requestBuilder.toString()); //Log your request where you need it
StringBuilder responseBuilder = new StringBuilder();
responseBuilder.append(response.getStatusLine());
responseBuilder.append("\n");
responseBuilder.append(response.getBody());
log.info(responseBuilder.toString()); //Log your response where you need it
return response;
}
}
And then use it in RestAssured request:
Filter logFilter = new CustomLogFilter();
RestAssured.given()
.filter(logFilter)
.when()
.get("/api");
We would need to convert first Response into String and then use this string reference in logs or scenario.write. I am trying to explain in below code -
String responseString, placeid;
private static Logger log =LogManager.getLogger(basics3.class.getName());
Properties prop=new Properties();
#Before
public void getData() throws IOException
{
FileInputStream fis=new FileInputStream(System.getProperty("user.dir")+"//env.properties");
prop.load(fis);
}
#Test
public void AddandDeletePlace()
{
RestAssured.baseURI= prop.getProperty("HOST");
Response res=given().
queryParam("key",prop.getProperty("KEY")).
body(payLoad.getPostData()).
when().
post(resources1.placePostData()).
then().assertThat().statusCode(200).and().contentType(ContentType.JSON).and().
body("status",equalTo("OK")).
extract().response();
responseString=res.asString();
JsonPath js= new JsonPath(responseString);
placeid=js.get("place_id");
}
#After
public void setLogs(){
log.info(responseString);
log.info(placeid);
}
Hope this gives you some vision of printing REST Assured logs.
I tried for several days to use the RESTEasy Client-Proxy with Multipart forms.
In the best scenario, I would like to pass a MultipartFile into the Proxy.
E.g.
//client:
//Resteasy proxy creation left out for brevity
public Response add(MultipartFile versionFile) {
proxy.add(versionFile);
}
//server (interface):
#POST
#Consumes({MediaType.MULTIPART_FORM_DATA})
FormularDTO add(MultipartFile versionFile);
This always ends in an Exception.
could not find writer for content-type multipart/form-data type: org.springframework.web.multipart.support
As suggested by the Docs, there a two ways to handle Multipart-Files:
a) MultipartOutput/MultipartInput:
What should I send via the Proxy? If I send a MultipartOutput, I get the same Exception. MultipartInput is Abstract.
b) Use DTO with #MultipartForm
The solution currently used in the project, but requires to map all File-Metadata, create a new DTO, etc.
See Example below:
//DTO
public class MultipartFileDataDTO {
#FormParam("file")
#PartType(MediaType.APPLICATION_OCTET_STREAM)
private InputStream file;
#FormParam("contentType")
#PartType(MediaType.TEXT_PLAIN)
private String contentType;
...
}
//Server-Interface
#POST
#Consumes({MediaType.MULTIPART_FORM_DATA})
FormularDTO add(#MultipartForm MultipartFileDataDTO versionFile);
//Client-Mapping
MultipartFileDataDTO upload = new MultipartFileDataDTO();
upload.setFile(versionFile.getInputStream());
upload.setContentType(versionFile.getContentType());
...
My Question: What is the easiest way to "pass" a MultipartFile via a generated RESTEasy-Client-Proxy?
I think the easiest way to do would be to create a simple MultiplartFormDataOutput object and send it to the proxy.
Here is a simple example:
MultipartFormDataOutput output = new MultipartFormDataOutput();
// It is possible to pass a File object or a InputStream in the addFormData
output.addFormData("file", fileObject, MediaType.APPLICATION_OCTET_STREAM_TYPE, filename);
proxy.add(output)
This question is a result of some work I'm doing with the Spring Security Oauth2 library. I've set up an oauth2 authorization server and an oauth2 resource server, the latter of which is meant to authorize based on access tokens.
The problem is that normally access tokens are passed in a header, but the big client we're setting this up for wants to pass the access token in a JSON request body. There's an interface you can use to set up custom access token extraction, but it looks like this:
public interface TokenExtractor {
/**
* Extract a token value from an incoming request without authentication.
*
* #param request the current ServletRequest
* #return an authentication token whose principal is an access token (or null if there is none)
*/
Authentication extract(HttpServletRequest request);
}
So, as best I can tell, all I have access to is the raw HTTPServletRequest, from which I need to deserialize the request and extract the access token.
Further complicating things, though, is the fact that the request body also contains other parameters needed for processing, so I want to deserialize it to a DTO class that I pass into my controller, something like so:
#RequestMapping("/oauth/someresource")
#Transactional
public Map<String, String> resource(#AuthenticationPrincipal UserDetails userDetails,
#RequestBody ClientRequestDto clientRequestDto) {
// Do some processing based on the request dto
}
I tried manually deserializing the request in the token extractor, but then I get an error "java.lang.IllegalStateException: getReader() has already been called for this request".
I was brainstorming a few possible solutions that I could research, and so far I've come up with:
find a way to reset the input stream
deserialize the object in the Token Extractor, attach it to the raw request object, and just access the raw request object in my controller instead of using #RequestBody
like 2, but find a way to add a custom deserializer that fetches the object attached to the raw request instead of processing the request's input stream.
Anyways, those are just some thoughts, if anyone has any ideas in terms of an elegant way of solving this, I'd greatly appreciate it.
EDIT: I did find this question which is similar: Spring reading request body twice, and the last answer did have one possible solution (creating a decorator request class that allows multiple input stream reads and creating a filter early on in the filter chain that wraps the HttpServletRequest). It seems workable, but a little heavy duty, so I'll leave this up to see if anyone has any other ideas as well.
So I ended up finding yet another question that addressed this issue that I didn't see before posting (How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?). That one also suggested creating a decorator around the HttpServletRequest, so I adapted the info from http://www.myjavarecipes.com/how-to-read-post-request-data-twice-in-spring/, adding a protection against large requests.
Here's what I came up with, in case anyone has any feedback:
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
// We include a max byte size to protect against malicious requests, since this all has to be read into memory
public static final Integer MAX_BYTE_SIZE = 1_048_576; // 1 MB
private String _body;
public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
_body = "";
InputStream bounded = new BoundedInputStream(request.getInputStream(), MAX_BYTE_SIZE);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bounded));
String line;
while ((line = bufferedReader.readLine()) != null){
_body += line;
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes());
return new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener readListener) {
}
};
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
I used the following configuration:
#Bean
FilterRegistrationBean multiReadFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
MultiReadRequestFilter multiReadRequestFilter = new MultiReadRequestFilter();
registrationBean.setFilter(multiReadRequestFilter);
registrationBean.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER - 2);
registrationBean.setUrlPatterns(Sets.newHashSet("/path/here"));
return registrationBean;
}
I make a call to a server using Java in order to receive json information and I want to retrieve that information through a Rest Web Servie. Right now I have this code:
public class consult {
public static void consult1() the url that retrieves json);
InputStream response = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(response));
for (String line; (line = reader.readLine()) != null;) {
System.out.println(line);
}
reader.close();
}
This shows the Json perfectly on the console but how can I set the information into some variable so when I call this WS in my browser (/resources/consult/) shows the info according to this WS?
#Path("/consult")
public class ConsultJSON {
#GET
#Produces ("application/json")
public String getInfo() throws MalformedURLException, IOException {
return consult.consult1();
}
I suggest you checking out this tutorial on how to implement RESTful JSON service:
http://www.mkyong.com/webservices/jax-rs/integrate-jackson-with-resteasy/
The idea behind the proper setup is that you don't need to return serialized string from your method. You just return a domain object and instruct framework to serialize it through annotations.
And of course you'll find there examples on how to POST new objects or updates for it (in JSON).
In reality you really should pipe the InputStream from the response directly to your OutputStream. There are facilities for doing this piping automatically in TUS in IOUtils / DataFetcher: http://tus.svn.sourceforge.net/viewvc/tus/tjacobs/io/