How to add a custom header to all outbound http requests from a dropwizard service? - dropwizard

How can I add a custom header to all outbound org.apache.http.client.HttpClient requests from a dropwizard service? E.g., whenever there is an outbound http request from any class in my dropwizard service I want to add a header "X-Powered-By:foo" to it automatically.

I am assuming you want to add that to outgoing requests that you poke. So i'll add that first.
DW uses jersey (so I am assuming you are using a jersey client to do your pokes). The jersey client uses an apache client (or can use) to do the actual requests.
Jersey uses filters to do what you need it to do. In this case, since you want to add a header to all requests, you will need to use the ClientRequestFilter. Consider this example:
public class HeaderTest {
public static void main(String[] args) {
// create the client
Client newClient = ClientBuilder.newClient().register(MyClientRequestFilter.class).register(MyClientRequestPrintingFilter.class);
// make a request
newClient.target("http://google.com").request().get();
}
#Priority(1)
public static class MyClientRequestFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
System.out.println("Added header");
requestContext.getHeaders().add("X-Powered-By", "foo");
}
}
#Priority(2)
public static class MyClientRequestPrintingFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().forEach((x,y) -> System.out.println("headerKey:" + x + " HEadervalue:" + y));
}
}
}
The class "MyClientRequestFilter" is registered to every single request. This code will always be executed (e.g. add a header to each and every request).
The second filter simply prints all headers in the request. Running this code I get:
Added header
headerKey:X-Powered-By HEadervalue:[foo]
I believe this is what you need.
Alternatively (in case I misunderstood), you can add a header to each response your server is doing. And this would be the filter (note: ContainerResponseFilter) that will do the do:
public static class MyHeaderResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-Powered-By", "foo");
}
}
And the test:
artur#pandaadb:~/dev/eclipse/eclipse_jee$ curl -v "localhost:9085/api/test/asd"
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 24 Aug 2016 09:21:19 GMT
< Content-Type: application/json
< X-Powered-By: foo
< Vary: Accept-Encoding
< Content-Length: 5
<
* Connection #0 to host localhost left intact
Hello
I hope that helps!
Artur

Related

How to get both HTTP response body and Status when using Reactor Netty HTTP Client

I am using the Reactor Netty HTTP client here as a stand alone dependency, ie not via spring-webflux because I do not want to drag in Spring related dependencies
As can be seen from the documentation it is possible to make a request that returns HttpClientResponse
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.client.HttpClientResponse;
public class Application {
public static void main(String[] args) {
HttpClientResponse response =
HttpClient.create()
.get()
.uri("http://example.com/")
.response()
.block();
}
}
Thing is HttpClientResponse only contains the headers and the staus. As can be seen from its Java Docs here
Also from the example to consume data one can do
import reactor.netty.http.client.HttpClient;
public class Application {
public static void main(String[] args) {
String response =
HttpClient.create()
.get()
.uri("http://example.com/")
.responseContent()
.aggregate()
.asString()
.block();
}
}
But this only returns the http entity data as string. No information about the headers nor status code.
The problem I have now is I need to make a request and get a response that gives me both the headers, status etc alongside with the http response body.
I cannot seem to find how. Any ideas?qw
Take a look at the following methods:
Flux<V> response(BiFunction<HttpClientResponse,ByteBufFlux,Publisher<V>> receiver)
Mono<V> responseSingle(BiFunction<HttpClientResponse, ByteBufMono, Mono<V>> receiver)
They allow you to access response body, status, and http headers simultaneously.
For example using the responseSingle method you can do the following:
private Mono<Foo> getFoo() {
return httpClient.get()
.uri("foos/1")
.responseSingle(
(response, bytes) ->
bytes.asString()
.map(it -> new Foo(response.status().code(), it))
);
}
The code above translates the response into some domain object Foo defined as follows:
public static class Foo {
int status;
String response;
public Foo(int status, String response) {
this.status = status;
this.response = response;
}
}
The Foo object is null when the http response does not have a body. For example, if HttpStatus 403 is returned, the Foo object is null. I was able to check response code and return just status.
(resp, bytes)-> {
if (resp.status().code()=HttpResponseStatus.OK.code) {
return bytes.asString().map(it->new Foo(resp.status(),it);
} else {
return Mono.just(new Foo(resp.status());
}
}

Spring AmqpAdmin create exchange & queues in vHost

A similar question to Send message to arbitrary vhost / exchange with RabbitMQ / Spring AMQP but I'm trying to have the AmqpAdmin create an exchange under a specific vHost
I tried doing something like
SimpleResourceHolder.bind(((RabbitAdmin) amqpAdmin).getRabbitTemplate().getConnectionFactory(), vhost);
...
amqpAdmin.declareExchange(exchange);
...
amqpAdmin.declareQueue(queue);
amqpAdmin.declareBinding(BindingBuilder.bind(queue).to(exchange).with(routingKey));
SimpleResourceHolder.unbind(((RabbitAdmin) amqpAdmin).getRabbitTemplate().getConnectionFactory());
However AmqpAdmin keeps using "/"
is there a way to tell it to use a specific vHost programmatically at runtime?
update 1:
based on #artem-bilan I've had (partial) success by doing:
public void sendToTopic(String domain, String topic, String routingKey, Object payload) {
bindToVirtualHost(template, domain);
try {
template.setUsePublisherConnection(true);
template.convertAndSend(topic, routingKey, payload);
} finally {
unbindFromVirtualHost(template);
template.setUsePublisherConnection(false);
}
}
private void bindToVirtualHost(RabbitTemplate rabbitTemplate, String vHost) {
AbstractConnectionFactory factory = (AbstractConnectionFactory) rabbitTemplate.getConnectionFactory();
LOG.debug("binding {} to {}", factory, vHost);
factory.setVirtualHost(vHost);
}
private void unbindFromVirtualHost(RabbitTemplate rabbitTemplate) {
AbstractConnectionFactory factory = (AbstractConnectionFactory) rabbitTemplate.getConnectionFactory();
LOG.debug("unbinding {} back to default {}", factory, DEFAULT_VHOST);
factory.setVirtualHost(DEFAULT_VHOST);
}
I say (partial) because if I do:
// pre :Manually create vHost foo
sendToTopic("bar","myTopic","key","The payload"); // connection error; protocol method: #method<connection.close>(reply-code=530, reply-text=NOT_ALLOWED - vhost TNo not found, as expected
sendToTopic("foo","myTopic","key","The payload2"); // success, as expected
sendToTopic("bar","myTopic","key","The payload3"); // success, NOT EXPECTED!
and the message of payload3 goes to vHost foo
The RabbitAdmin cannot do more than its ConnectionFactory allows. So, the vHost is similar to the host/port and it cannot be managed from end-user perspectiv.
See:
/**
* Create a new CachingConnectionFactory given a host name
* and port.
* #param hostNameArg the host name to connect to
* #param port the port number
*/
public CachingConnectionFactory(#Nullable String hostNameArg, int port) {
and its:
public void setVirtualHost(String virtualHost) {
The RabbitAdmin, on its turn, is just like this:
/**
* Construct an instance using the provided {#link ConnectionFactory}.
* #param connectionFactory the connection factory - must not be null.
*/
public RabbitAdmin(ConnectionFactory connectionFactory) {
So, to deal with different vHost, you need to have its own ConnectionFactory and RabbitAdmin.
No, AmqpAdmin cannot create for you a vHost. This is not an AMQP protocol operation.
See https://docs.spring.io/spring-amqp/docs/2.2.7.RELEASE/reference/html/#management-rest-api for more info.

Zuul dropping the added headers for few requests while making asynchronous requests from Angular

I have Zuul gateway as proxy between Angular app and our micro services. I have created a zuul filter to add few headers for every request.
#Component
public class RoutesAuthenticationFilter extends ZuulFilter {
#Override
public String filterType() {
return "route";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("principal", principal);
ctx.addZuulRequestHeader("customerheader", String.valueOf("testheader"));
return null;
}
This has been working for every request I made from postman.
I started seeing it is not adding/dropping these headers for the requests made to services through gateway from Angular app.
When I look at the log for headers in the request after adding them I see request contains the added headers are present for all requests
Headers from the requet after adding them all: { principal=john123, x-forwarded-host=localhost:8080, x-forwarded-proto=http, x-forwarded-prefix=/application-management, x-forwarded-port=8080, customerheader=testheader, x-forwarded-for=0:0:0:0:0:0:0:1}
But when I print them in the service, they are missing for few requests
2018-08-17 11:23:47.709 INFO [application-management,56a2962a54770966,56a2962a54770966,false] 10952 --- [nio-9090-exec-9] c.f.c.v.d.c.i.RequestInterceptor : Entered request interceptor
customer header from request: testheader
principal from req: john123
2018-08-17 11:23:47.713 INFO [application-management,058aad714328a61f,058aad714328a61f,false] 10952 --- [nio-9090-exec-2] c.f.c.v.d.c.i.RequestInterceptor : Entered request interceptor
customer header from request: null
principal from req: null
In the above output, headers are missing for one request and present for one request.
These two requests are made asynchronously from Angular app.
I do not see this problem if I make these two calls one in another, means if I make one call and wait for the response and make the next one then it is not missing headers

Guacamole SSH disconnecting, no keyboard

I'm using Guacamole 0.9.12-incubating - on server-side extending GuacamoleHTTPTunnelServlet, on client using official JavaScript code. Guacamole server is compiled from source and is running on Ubuntu 17.04.
SSH connection is successfully established, but disconnects after 15 seconds. No keyboard strokes nor mouse are working.
May 7 17:14:09 dev guacd[4071]: Creating new client for protocol "ssh"
May 7 17:14:09 dev guacd[4071]: Connection ID is "$30e2e833-5640-4bc9-92c3-929ced3d6e0e"
May 7 17:14:09 dev guacd[4382]: User "#4209df46-e26a-4ced-93c4-c264578f85a5" joined connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" (1 users now present)
May 7 17:14:10 dev guacd[4382]: SSH connection successful.
May 7 17:14:24 dev guacd[4382]: User is not responding.
May 7 17:14:24 dev guacd[4382]: User "#4209df46-e26a-4ced-93c4-c264578f85a5" disconnected (0 users remain)
May 7 17:14:24 dev guacd[4382]: Last user of connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" disconnected
May 7 17:14:25 dev guacd[4382]: SSH connection ended.
May 7 17:14:25 dev guacd[4071]: Connection "$30e2e833-5640-4bc9-92c3-929ced3d6e0e" removed.
Client JavaScript is the same as in documentation - https://guacamole.incubator.apache.org/doc/gug/writing-you-own-guacamole-app.html .
When I Override methods in servlet, they show me that key strokes go to it. So problem is probably somewhere between servlet and guacd?
#Override
protected void doWrite(HttpServletRequest request, HttpServletResponse response, String tunnelUUID) throws GuacamoleException {
LOGGER.debug("Do WRITE to session " + tunnelUUID);
super.doWrite(request, response, tunnelUUID);
}
#Override
protected void doRead(HttpServletRequest request, HttpServletResponse response, String tunnelUUID) throws GuacamoleException {
LOGGER.debug("Do read to session " + tunnelUUID);
super.doRead(request, response, tunnelUUID);
}
Connection is established, but no key strokes are working:
Thanks.
Problem was in Spring Boot, as discussed there - https://glyptodon.org/jira/si/jira.issueviews:issue-html/GUAC-1252/GUAC-1252.html .
Solution is to create own implementation of HiddenHttpMethodFilter:
#Configuration
public class GuacamoleServletConfiguration {
#Bean
public GuacamoleServlet guacamoleTunnelServlet() {
return new GuacamoleServlet();
}
#Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean(guacamoleTunnelServlet(), "/remote/tunnel");
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
#Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new HiddenHttpMethodFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if ("POST".equals(request.getMethod()) && request.getRequestURI().startsWith("/remote/tunnel")) {
filterChain.doFilter(request, response);
} else {
super.doFilterInternal(request, response, filterChain);
}
}
};
}
}

How to consume basic-authentication protected Restful web service via feign client

Thank you for your time.
To make it simple, I created a example service like below:
#RestController
#RequestMapping("/")
public class ComputeController {
#GetMapping("/add")
public int add(#RequestParam("left") int left, #RequestParam("right") int right) {
return left + right;
}
}
To protected this url, I config spring-security like this:
management.security.enabled=true
security.user.name=admin
security.user.password=admin
When I startup this service and access like this:
GET /add?left=100&right=11 HTTP/1.1
Authorization: ***** Hidden credentials *****
Host: localhost:7777
Connection: close
Everythis is going fine.
In other node, I created a "service-comsumer" by netflix feign. It's a Java Interface.
#FeignClient(name = "API-GATEWAY", path = "/compute-service", fallback = ComputeServiceCircuitBreaker.class)
public interface ComputeServiceClient {
#RequestMapping(path = "/add", method = RequestMethod.GET)
public Integer add(#RequestParam("left") Integer left, #RequestParam("right") Integer right);
}
But I DO NOT know how to config the request header "Authorization".
Any idea? Thanks again.
You need to create a FeignClient Configuration class, for example
import feign.auth.BasicAuthRequestInterceptor;
#Configuration
public class FeignClientConfiguration {
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("admin", "admin");
}
}
then in your #FeignClient annotation use this configuration file:
#FeignClient(name="service", configuration = FeignClientConfiguration.class)
As of october 2020, this works:
public class FeignClientConfiguration {
#Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("asdf", "asdf");
}
}
#FeignClient(name = "thirdPartyClient", url = "ceva.com",
configuration = FeignClientConfiguration.class)
public interface ThirdPartyClient {
#GetMapping
Response get();
}
Note, we don't annotate the configuration with #Configuration in order to not apply it to all requests.

Resources