spring/logback - log http request body for errors only - spring

I am using MDC inside an interceptor to add http request related fields to my logging. Now, i want to log the request body along any log of level error or fatal and/or for exceptions as well. I am totally new to MDC, spring or java in general.
The providers section of my logback-spring.xml looks currently like this:
<providers>
<timestamp>
<!-- <fieldName>timestamp</fieldName> -->
<timeZone>UTC</timeZone>
</timestamp>
<version/>
<nestedField>
<fieldName>log</fieldName>
<providers>
<logLevel />
</providers>
</nestedField>
<message/>
<loggerName/>
<threadName/>
<context/>
<pattern>
<omitEmptyFields>true</omitEmptyFields>
<pattern>
{
"trace": {
"id": "%mdc{X-B3-TraceId}",
"span_id": "%mdc{X-B3-SpanId}",
"parent_span_id": "%mdc{X-B3-ParentSpanId}",
"exportable": "%mdc{X-Span-Export}"
}
}
</pattern>
</pattern>
<mdc>
<excludeMdcKeyName>traceId</excludeMdcKeyName>
<excludeMdcKeyName>spanId</excludeMdcKeyName>
<excludeMdcKeyName>parentId</excludeMdcKeyName>
<excludeMdcKeyName>spanExportable</excludeMdcKeyName>
<excludeMdcKeyName>X-RequestId</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-TraceId</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-SpanId</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-ParentSpanId</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-Sampled</excludeMdcKeyName>
<excludeMdcKeyName>X-B3-Flags</excludeMdcKeyName>
<excludeMdcKeyName>B3</excludeMdcKeyName>
<excludeMdcKeyName>X-Span-Export</excludeMdcKeyName>
</mdc>
<stackTrace/>
</providers>
```

It will be a bit of advice than a ready-made solution, but I think the solution may not be so obvious.
In general, if you read payload once from the request, from InputStream of HttpRequest, it may no longer be available for the rest of the spring application. (Spring probably does (or can do) a proxy object on it to store a read request payload from InputStream to access it several times).
I think, you can create HTTP filter and set MDC context after processing HTTP request - but In that case you can only log error message with body after processing this request, not during (when you log messages usually you don't know if it will end up this http request with 500 or not).
If we talking about Spring, you have here abstract filter class that can help you: org.springframework.web.filter.AbstractRequestLoggingFilter
Here is simple filter created by me (but not tested), based on source of AbstractRequestLoggingFilter
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
#Component
public class TestFilter extends OncePerRequestFilter {
private static final int MAX_PAYLOAD_LENGTH = 50_000;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if (!(request instanceof ContentCachingRequestWrapper)) {
requestToUse = new ContentCachingRequestWrapper(request, MAX_PAYLOAD_LENGTH);
}
try {
filterChain.doFilter(requestToUse, response);
} catch (RuntimeException | ServletException | IOException e) {
MDC.put("request-payload", convertRequestPayloadToString(request));
// You can also log your exception here
throw e;
} finally {
if (response.getStatus() != 200) {
MDC.put("request-payload", convertRequestPayloadToString(request));
}
}
}
private String convertRequestPayloadToString(HttpServletRequest request) {
ContentCachingRequestWrapper wrapper =
WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
StringBuilder sb = new StringBuilder();
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
int length = Math.min(buf.length, MAX_PAYLOAD_LENGTH);
String payload;
try {
payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException ex) {
payload = "[unknown]";
}
sb.append(payload);
}
}
return sb.toString();
}
}

Related

How can i implement slf4j MDC in springboot webflux [duplicate]

I referenced with the blog post Contextual Logging with Reactor Context and MDC but I don't know how to access reactor context in WebFilter.
#Component
public class RequestIdFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
List<String> myHeader = exchange.getRequest().getHeaders().get("X-My-Header");
if (myHeader != null && !myHeader.isEmpty()) {
MDC.put("myHeader", myHeader.get(0));
}
return chain.filter(exchange);
}
}
Here's one solution based on the latest approach, as of May 2021, taken from the official documentation:
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Signal;
import reactor.util.context.Context;
#Slf4j
#Configuration
public class RequestIdFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String requestId = getRequestId(request.getHeaders());
return chain
.filter(exchange)
.doOnEach(logOnEach(r -> log.info("{} {}", request.getMethod(), request.getURI())))
.contextWrite(Context.of("CONTEXT_KEY", requestId));
}
private String getRequestId(HttpHeaders headers) {
List<String> requestIdHeaders = headers.get("X-Request-ID");
return requestIdHeaders == null || requestIdHeaders.isEmpty()
? UUID.randomUUID().toString()
: requestIdHeaders.get(0);
}
public static <T> Consumer<Signal<T>> logOnEach(Consumer<T> logStatement) {
return signal -> {
String contextValue = signal.getContextView().get("CONTEXT_KEY");
try (MDC.MDCCloseable cMdc = MDC.putCloseable("MDC_KEY", contextValue)) {
logStatement.accept(signal.get());
}
};
}
public static <T> Consumer<Signal<T>> logOnNext(Consumer<T> logStatement) {
return signal -> {
if (!signal.isOnNext()) return;
String contextValue = signal.getContextView().get("CONTEXT_KEY");
try (MDC.MDCCloseable cMdc = MDC.putCloseable("MDC_KEY", contextValue)) {
logStatement.accept(signal.get());
}
};
}
}
Given you have the following line in your application.properties:
logging.pattern.level=[%X{MDC_KEY}] %5p
then every time an endpoint is called your server logs will contain a log like this:
2021-05-06 17:07:41.852 [60b38305-7005-4a05-bac7-ab2636e74d94] INFO 20158 --- [or-http-epoll-6] my.package.RequestIdFilter : GET http://localhost:12345/my-endpoint/444444/
Every time you want to log manually something within a reactive context you will have add the following to your reactive chain:
.doOnEach(logOnNext(r -> log.info("Something")))
If you want the X-Request-ID to be propagated to other services for distributed tracing, you need to read it from the reactive context (not from MDC) and wrap your WebClient code with the following:
Mono.deferContextual(
ctx -> {
RequestHeadersSpec<?> request = webClient.get().uri(uri);
request = request.header("X-Request-ID", ctx.get("CONTEXT_KEY"));
// The rest of your request logic...
});
You can do something similar to below, You can set the context with any class you like, for this example I just used headers - but a custom class will do just fine.
If you set it here, then any logging with handlers etc will also have access to the context.
The logWithContext below, sets the MDC and clears it after. Obviously this can be replaced with anything you like.
public class RequestIdFilter implements WebFilter {
private Logger LOG = LoggerFactory.getLogger(RequestIdFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
HttpHeaders headers = exchange.getRequest().getHeaders();
return chain.filter(exchange)
.doAfterSuccessOrError((r, t) -> logWithContext(headers, httpHeaders -> LOG.info("Some message with MDC set")))
.subscriberContext(Context.of(HttpHeaders.class, headers));
}
static void logWithContext(HttpHeaders headers, Consumer<HttpHeaders> logAction) {
try {
headers.forEach((name, values) -> MDC.put(name, values.get(0)));
logAction.accept(headers);
} finally {
headers.keySet().forEach(MDC::remove);
}
}
}
As of Spring Boot 2.2 there is Schedulers.onScheduleHook that enables you to handle MDC:
Schedulers.onScheduleHook("mdc", runnable -> {
Map<String, String> map = MDC.getCopyOfContextMap();
return () -> {
if (map != null) {
MDC.setContextMap(map);
}
try {
runnable.run();
} finally {
MDC.clear();
}
};
});
Alternatively, Hooks.onEachOperator can be used to pass around the MDC values via subscriber context.
http://ttddyy.github.io/mdc-with-webclient-in-webmvc/
This is not full MDC solution, e.g. in my case I cannot cleanup MDC values in R2DBC threads.
UPDATE: this article really solves my MDC problem: https://www.novatec-gmbh.de/en/blog/how-can-the-mdc-context-be-used-in-the-reactive-spring-applications/
It provides correct way of updating MDC based on subscriber context.
Combine it with SecurityContext::class.java key populated by AuthenticationWebFilter and you will be able to put user login to your logs.
My solution based on Reactor 3 Reference Guide approach but using doOnSuccess instead of doOnEach.
The main idea is to use Context for MDC propagation in the next way:
Fill a downstream Context (which will be used by derived threads) with the MDC state from an upstream flow (can be done by .contextWrite(context -> Context.of(MDC.getCopyOfContextMap())))
Access the downstream Context in derived threads and fill MDC in derived thread with values from the downstream Context (the main challenge)
Clear the MDC in the downstream Context (can be done by .doFinally(signalType -> MDC.clear()))
The main problem is to access a downstream Context in derived threads. And you can implement step 2 with the most convenient for you approach). But here is my solution:
webclient.post()
.bodyValue(someRequestData)
.retrieve()
.bodyToMono(String.class)
// By this action we wrap our response with a new Mono and also
// in parallel fill MDC with values from a downstream Context because
// we have an access to it
.flatMap(wrapWithFilledMDC())
.doOnSuccess(response -> someActionWhichRequiresFilledMdc(response)))
// Fill a downstream context with the current MDC state
.contextWrite(context -> Context.of(MDC.getCopyOfContextMap()))
// Allows us to clear MDC from derived threads
.doFinally(signalType -> MDC.clear())
.block();
// Function which implements second step from the above main idea
public static <T> Function<T, Mono<T>> wrapWithFilledMDC() {
// Using deferContextual we have an access to downstream Context, so
// we can just fill MDC in derived threads with
// values from the downstream Context
return item -> Mono.deferContextual(contextView -> {
// Function for filling MDC with Context values
// (you can apply your action)
fillMdcWithContextView(contextView);
return Mono.just(item);
});
}
public static void fillMdcWithContextValues(ContextView contextView) {
contextView.forEach(
(key, value) -> {
if (key instanceof String keyStr && value instanceof String valueStr) {
MDC.put(keyStr, valueStr);
}
});
}
This approach is also can be applied to doOnError and onErrorResume methods since the main idea is the same.
Used versions:
spring-boot: 2.7.3
spring-webflux: 5.3.22 (from spring-boot)
reactor-core: 3.4.22 (from spring-webflux)
reactor-netty: 1.0.22 (from spring-webflux)
I achieved this with :-
package com.nks.app.filter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
* #author nks
*/
#Component
#Slf4j
public class SessionIDFilter implements WebFilter {
private static final String APP_SESSION_ID = "app-session-id";
/**
* Process the Web request and (optionally) delegate to the next
* {#code WebFilter} through the given {#link WebFilterChain}.
*
* #param serverWebExchange the current server exchange
* #param webFilterChain provides a way to delegate to the next filter
* #return {#code Mono<Void>} to indicate when request processing is complete
*/
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
serverWebExchange.getResponse()
.getHeaders().add(APP_SESSION_ID, serverWebExchange.getRequest().getHeaders().getFirst(APP_SESSION_ID));
MDC.put(APP_SESSION_ID, serverWebExchange.getRequest().getHeaders().getFirst(APP_SESSION_ID));
log.info("[{}] : Inside filter of SessionIDFilter, ADDED app-session-id in MDC Logs", MDC.get(APP_SESSION_ID));
return webFilterChain.filter(serverWebExchange);
}
}
and, values associated with app-session-id for the thread can be logged.

How to register my custom MessageBodyReader in my CLIENT?

Maybe somebody can help me find out how to solve this.
I am using jersey-apache-client 1.17
I tried to use Jersey client to build a standalone application (no Servlet container or whatever, just the Java classes) which communicates with a RESTFUL API, and everything worked fine until I tried to handle the mediatype "text/csv; charset=utf-8" which is a CSV stream sent by the server.
The thing is that I can read this stream with the following code:
InputStreamReader reader = new InputStreamReader(itemExportBuilder
.get(ClientResponse.class).getEntityInputStream());
Csv csv = new Csv();
Input input = csv.createInput(reader);
try {
String[] readLine;
while ((readLine = input.readLine()) != null) {
LOG.debug("Reading CSV: {}", readLine);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
But I'd like to encapsulate it and put it into a MessageBodyReader. But after writing this code, I just can't make the client use the following class:
package client.response;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Provider
public class ItemExportMessageBodyReader implements MessageBodyReader<ItemExportResponse> {
private static final Logger LOG = LoggerFactory.getLogger(ItemExportMessageBodyReader.class);
private static final Integer SKU = 0;
private static final Integer BASE_SKU = 1;
public boolean isReadable(Class<?> paramClass, Type type, Annotation[] annotations,
MediaType mediaType) {
LOG.info("Cheking if content is readable or not");
return paramClass == ItemExportResponse.class && !mediaType.isWildcardType()
&& !mediaType.isWildcardSubtype()
&& mediaType.isCompatible(MediaType.valueOf("text/csv; charset=utf-8"));
}
public ItemExportResponse readFrom(Class<ItemExportResponse> paramClass, Type paramType,
Annotation[] paramArrayOfAnnotation, MediaType paramMediaType,
MultivaluedMap<String, String> paramMultivaluedMap, InputStream entityStream)
throws IOException, WebApplicationException {
InputStreamReader reader = new InputStreamReader(entityStream);
Csv csv = new Csv();
Input input = csv.createInput(reader);
List<Item> items = new ArrayList<Item>();
try {
String[] readLine;
while ((readLine = input.readLine()) != null) {
LOG.trace("Reading CSV: {}", readLine);
Item item = new Item();
item.setBaseSku(readLine[BASE_SKU]);
items.add(item);
}
} catch (IOException e) {
LOG.warn("Item export HTTP response handling failed", e);
} finally {
try {
input.close();
} catch (IOException e) {
LOG.warn("Could not close the HTTP response stream", e);
}
}
ItemExportResponse response = new ItemExportResponse();
response.setItems(items);
return response;
}
}
The following documentation says that the preferred way of making this work in a JAX-RS client to register the message body reader with the code below:
Using Entity Providers with JAX-RS Client API
Client client = ClientBuilder.newBuilder().register(MyBeanMessageBodyReader.class).build();
Response response = client.target("http://example/comm/resource").request(MediaType.APPLICATION_XML).get();
System.out.println(response.getStatus());
MyBean myBean = response.readEntity(MyBean.class);
System.out.println(myBean);
Now the thing is that I can't use the ClientBuilder. I have to extend from a specific class which constructs the client another way, and I have no access to change the construction.
So when I receive the response from the server, the client fails with the following Exception:
com.sun.jersey.api.client.ClientHandlerException: A message body reader for Java class client.response.ItemExportResponse, and Java type class client.response.ItemExportResponse, and MIME media type text/csv; charset=utf-8 was not found
Any other way to register my MessageBodyReader?
OK. If anybody would bump into my question I solved this mystery by upgrading from Jersey 1.17 to version 2.9. The documentation I linked above also covers this version not the old one, this is where the confusion stems from.
Jersey introduced backward INCOMPATIBLE changes starting from version 2, so I have no clue how to configure it in version 1.17.
In version 2 the proposed solution worked fine.

CXF InInterceptor not firing

I have created web service. It works fine. Now I'm trying to implement authentication to it. I'm using CXF interceptors for that purpose. For some reason interceptors won't fire. What am I missing? This is my first web service.
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.xml.ws.WebServiceContext;
import org.apache.cxf.interceptor.InInterceptors;
#WebService
#InInterceptors(interceptors = "ws.BasicAuthAuthorizationInterceptor")
public class Service {
#WebMethod
public void test(#WebParam(name = "value") Integer value) throws Exception {
System.out.println("Value = " + value);
}
}
-
package ws;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.cxf.binding.soap.interceptor.SoapHeaderInterceptor;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
public class BasicAuthAuthorizationInterceptor extends SoapHeaderInterceptor {
#Override
public void handleMessage(Message message) throws Fault {
System.out.println("**** GET THIS LINE TO CONSOLE TO SEE IF INTERCEPTOR IS FIRING!!!");
AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
// If the policy is not set, the user did not specify credentials.
// 401 is sent to the client to indicate that authentication is required.
if (policy == null) {
sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
return;
}
String username = policy.getUserName();
String password = policy.getPassword();
// CHECK USERNAME AND PASSWORD
if (!checkLogin(username, password)) {
System.out.println("handleMessage: Invalid username or password for user: "
+ policy.getUserName());
sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
}
}
private boolean checkLogin(String username, String password) {
if (username.equals("admin") && password.equals("admin")) {
return true;
}
return false;
}
private void sendErrorResponse(Message message, int responseCode) {
Message outMessage = getOutMessage(message);
outMessage.put(Message.RESPONSE_CODE, responseCode);
// Set the response headers
#SuppressWarnings("unchecked")
Map<String, List<String>> responseHeaders = (Map<String, List<String>>) message
.get(Message.PROTOCOL_HEADERS);
if (responseHeaders != null) {
responseHeaders.put("WWW-Authenticate", Arrays.asList(new String[] { "Basic realm=realm" }));
responseHeaders.put("Content-Length", Arrays.asList(new String[] { "0" }));
}
message.getInterceptorChain().abort();
try {
getConduit(message).prepare(outMessage);
close(outMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
private Message getOutMessage(Message inMessage) {
Exchange exchange = inMessage.getExchange();
Message outMessage = exchange.getOutMessage();
if (outMessage == null) {
Endpoint endpoint = exchange.get(Endpoint.class);
outMessage = endpoint.getBinding().createMessage();
exchange.setOutMessage(outMessage);
}
outMessage.putAll(inMessage);
return outMessage;
}
private Conduit getConduit(Message inMessage) throws IOException {
Exchange exchange = inMessage.getExchange();
EndpointReferenceType target = exchange.get(EndpointReferenceType.class);
Conduit conduit = exchange.getDestination().getBackChannel(inMessage, null, target);
exchange.setConduit(conduit);
return conduit;
}
private void close(Message outMessage) throws IOException {
OutputStream os = outMessage.getContent(OutputStream.class);
os.flush();
os.close();
}
}
I'm fighting with this for few days now. Don't know what to google any more. Help is appreciated.
I've found solution. I was missing the following line in MANIFEST.MF file in war project:
Dependencies: org.apache.cxf
Maven wasn't includint this line by himself so I had to find workaround. I found about that here. It says: When using annotations on your endpoints / handlers such as the Apache CXF ones (#InInterceptor, #GZIP, ...) remember to add the proper module dependency in your manifest. Otherwise your annotations are not picked up and added to the annotation index by JBoss Application Server 7, resulting in them being completely and silently ignored.
This is where I found out how to change MANIFEST.MF file.
In short, I added custom manifest file to my project and referenced it in pom.xml. Hope this helps someone.
The answer provided by Felix is accurate. I managed to solve the problem using his instructions. Just for completion here is the maven config that lets you use your own MANIFEST.MF file placed in the META-INF folder.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
and here is the relevant content of the content of the MANIFEST.MF file I was using.
Manifest-Version: 1.0
Description: yourdescription
Dependencies: org.apache.ws.security,org.apache.cxf

How to create a Filter as a component and what should I put for annotation?

So, I am working on CQ5. I would like to deploy a bundled component as a service to filter & modify the .inifinity.json output (from sling) to the CQ5.
I am able to build and deploy, and have both the component and bundle being active. However, when a page or call an infinity.json , I don't see the output in log. I suspect because the services not properly installed? or some other service return the call before running my service? not sure. and here is my code:
package com.my.test;
import javax.servlet.*;
import java.io.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.annotation.component.*;
#Component(
provide=Filter.class,
immediate=true
)
public class TestFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(TestFilter.class);
private FilterConfig filterConfig;
public void init (FilterConfig filterConfig) {
LOGGER.info ("INIT .");
this.setFilterConfig(filterConfig);
}
public void destroy() {
LOGGER.info ("Destroy me NOW!!...");
}
public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain){
try
{
LOGGER.info ("Within Simple Filter ... :) ");
LOGGER.info ("Filtering the Request ...");
chain.doFilter (request, response);
LOGGER.info ("Within Simple Filter ... ");
LOGGER.info ("Filtering the Response ...");
} catch (IOException io) {
LOGGER.info ("IOException raised in SimpleFilter");
} catch (ServletException se) {
LOGGER.info ("ServletException raised in SimpleFilter");
}
}
public FilterConfig getFilterConfig() {
return this.filterConfig;
}
public void setFilterConfig (FilterConfig filterConfig){
this.filterConfig = filterConfig;
}
}
Am I missing anything in the annotation? or anything that I should have done?
Looking the discussion threads here and here, it looks like you need to add annotations to set a sling.filter.scope #Property, and also to declare the #Service.
Something like this:
#Component(
provide=Filter.class,
immediate=true
)
#Service(javax.servlet.Filter.class)
#Properties({
#Property(name = "sling.filter.scope", value = "request")
})
The Sling integration test services source code include a few Filters at [1], that you can use as examples. As David says, you're probably just missing the #Service annotation.
http://svn.apache.org/repos/asf/sling/trunk/launchpad/test-services/src/main/java/org/apache/sling/launchpad/testservices/filters/

How to convert a string to an Apache HttpComponents HttpRequest

I have a String that contains an HTTP header. I want to turn this into an Apache HttpComponents HttpRequest object. Is there a way to do this without picking apart the string myself?
This tutorial: http://hc.apache.org/httpcomponents-core-dev/tutorial/html/fundamentals.html#d5e56 and the javadoc does not indicate as much.
A class to convert a string to apache request:
import org.apache.http.*;
import org.apache.http.impl.DefaultHttpRequestFactory;
import org.apache.http.impl.entity.EntityDeserializer;
import org.apache.http.impl.entity.LaxContentLengthStrategy;
import org.apache.http.impl.io.AbstractSessionInputBuffer;
import org.apache.http.impl.io.HttpRequestParser;
import org.apache.http.io.HttpMessageParser;
import org.apache.http.io.SessionInputBuffer;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicLineParser;
import org.apache.http.params.BasicHttpParams;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
*
*/
public class ApacheRequestFactory {
public static HttpRequest create(final String requestAsString) {
try {
SessionInputBuffer inputBuffer = new AbstractSessionInputBuffer() {
{
init(new ByteArrayInputStream(requestAsString.getBytes()), 10, new BasicHttpParams());
}
#Override
public boolean isDataAvailable(int timeout) throws IOException {
throw new RuntimeException("have to override but probably not even called");
}
};
HttpMessageParser parser = new HttpRequestParser(inputBuffer, new BasicLineParser(new ProtocolVersion("HTTP", 1, 1)), new DefaultHttpRequestFactory(), new BasicHttpParams());
HttpMessage message = parser.parse();
if (message instanceof BasicHttpEntityEnclosingRequest) {
BasicHttpEntityEnclosingRequest request = (BasicHttpEntityEnclosingRequest) message;
EntityDeserializer entityDeserializer = new EntityDeserializer(new LaxContentLengthStrategy());
HttpEntity entity = entityDeserializer.deserialize(inputBuffer, message);
request.setEntity(entity);
}
return (HttpRequest) message;
} catch (IOException e) {
throw new RuntimeException(e);
} catch (HttpException e) {
throw new RuntimeException(e);
}
}
}
and a test class showing how to use it:
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import static org.junit.Assert.*;
/**
*
*/
public class ApacheRequestFactoryTest {
#Test
public void testGet() {
String requestString = "GET /?one=aone&two=atwo HTTP/1.1\n" +
"Host: localhost:7788\n" +
"Connection: Keep-Alive\n" +
"User-Agent: Apache-HttpClient/4.0.1 (java 1.5)";
HttpRequest request = ApacheRequestFactory.create(requestString);
assertEquals("GET", request.getRequestLine().getMethod());
List<NameValuePair> pairs = URLEncodedUtils.parse(URI.create(request.getRequestLine().getUri()), "ISO-8859-1");
checkPairs(pairs);
}
#Test
public void testPost() throws IOException {
String requestString = "POST / HTTP/1.1\n" +
"Content-Length: 17\n" +
"Content-Type: application/x-www-form-urlencoded; charset=ISO-8859-1\n" +
"Host: localhost:7788\n" +
"Connection: Keep-Alive\n" +
"User-Agent: Apache-HttpClient/4.0.1 (java 1.5)\n" +
"\n" +
"one=aone&two=atwo";
HttpRequest request = ApacheRequestFactory.create(requestString);
assertEquals("POST", request.getRequestLine().getMethod());
List<NameValuePair> pairs = URLEncodedUtils.parse(((BasicHttpEntityEnclosingRequest)request).getEntity());
checkPairs(pairs);
}
private void checkPairs(List<NameValuePair> pairs) {
for (NameValuePair pair : pairs) {
if (pair.getName().equals("one")) assertEquals("aone", pair.getValue());
else if (pair.getName().equals("two")) assertEquals("atwo", pair.getValue());
else assertTrue("got more parameters than expected:"+pair.getName(), false);
}
}
}
And a small rant:
WHAT ARE THE APACHE HTTP TEAM THINKING ? The api is incredibly awkward to use. Developers around the world are wasting time writing wrapper and conversion classes for what should be run of the mill every day usage (like this example the simple act of converting a string to an apache http request, and the bizarre way you need to extract the form parameters (also having to do it in two different ways depending on what type of request was made)). The global time wasted because of this is huge. When you write an API from the bottom up, starting with the specs, you MUST then start a layer from the top down (top being an interface where you can get typical work done without having to understand or look at the way the code is implemented), making every day usage of the library CONVENIENT and intuitive. Apache http libraries are anything but. It's almost a miracle that its the standard library for this type of task.

Resources