Spring Cloud Contract for provider - setting optional header - spring-cloud-contract

I've created a contract on a provider side:
Contract.make {
request {
method 'GET'
url('/cars/car?id=3')
headers {
header(accept(), "application/hal+json")
header(SOME OTHER HEADER)
}
}
response {
...
}
}
Unfortunately one of my customers do not send request with "header(SOME OTHER HEADER)". My question is how can I mark "header(SOME OTHER HEADER)" as optional?

You can have the value optional. Not the header itself. If the header is optional then create two separate contracts. One with the header and one without it.

I'm experiencing the same issue here. I need to include Access-Control-Allow-Origin header to the generated stub but if I add it to the contract the tests begins to fail becouse of:
Expecting:
<null>
to be equal to:
<"\"Access-Control-Allow-Origin\": \"*\"">
even if I annotate the RestController class with #CrossOrigin(value = "*")

Related

How can I log every request/response body in Armeria HTTP client

I tried two approaches to log every HTTP body produced/received by my armeria client:
using out-of-box LoggingClient decorator
decorator(LoggingClient.newDecorator())
creating custom logging decorator
decorator { delegate, ctx, req ->
ctx.log().whenRequestComplete().thenAccept { log -> logger.trace(log.toStringRequestOnly()) }
ctx.log().whenComplete().thenAccept { log -> logger.trace(log.toStringResponseOnly()) }
delegate.execute(ctx, req)
}
But I see in logs only headers and other technical information. How can I log requestContent/responseContent?
It's said in armeria documentation that these fields are available only for Thrift clients:
the serialization-dependent content object of the request. ThriftCall for Thrift. null otherwise.
the serialization-dependent content object of the response. ThriftReply for Thrift. null otherwise.
It's weird to me.
I had to add com.linecorp.armeria.client.logging.ContentPreviewingClient decorator in addition to logging decorator:
decorator(ContentPreviewingClient.newDecorator(1000))

Spring Contracts generated from Spring RestDocs: ignoring headers

TL;DR: I commented on this issue and got asked to open a new ticket, but then realized this is more of a question as Spring RestDocs provides a way to achieve what I want (ignoring unimportant headers in contracts) with operation preprocessor. So here we are, on our friendly SoF
The problem is I am trying to generate contracts starting from a RestDocs test (using RestAssured and junit5 if it matters). Test setup (in Kotlin) looks like:
private val defaultDocument = document("{method_name}", SpringCloudContractRestDocs.dslContract())
lateinit var spec: RequestSpecification
#BeforeEach
internal fun setUp(restDocumentationContextProvider: RestDocumentationContextProvider) {
RestAssured.port = port
spec = RequestSpecBuilder()
.setConfig(
RestAssuredConfig.config()
.objectMapperConfig(
ObjectMapperConfig.objectMapperConfig()
.jackson2ObjectMapperFactory { _, _ -> mapper }
)
)
.addFilter(defaultDocument)
.addFilter(ResponseLoggingFilter())
.log(LogDetail.ALL)
.build()
}
where mapper and port are injected as Spring beans.
The server generates a Date header, which is the time when the response is generated. This is done automatically by Spring WebMvc (I think) and I don't care at all for that header. However, the Date header causes stub generation to fail, as I decided to use Spring Cloud Contracts in a polyglot world approach to generate and upload stub to maven repository, because now the server generates a different date.
As I point out here, the ContractDslSnippet does not seem to provide a way to ignore unimportant headers and/or to add matchers (which would still an open question).
The (short) list of questions:
How can I filter out unimportant headers from generated contracts?
Can I add custom matchers for headers, like I can do for body?
How to remove unimportant header, using Spring RestDocs preprocessors:
private val defaultDocument = document("{method_name}", SpringCloudContractRestDocs.dslContract())
lateinit var spec: RequestSpecification
#BeforeEach
internal fun setUp(restDocumentationContextProvider: RestDocumentationContextProvider) {
RestAssured.port = port
spec = RequestSpecBuilder()
.setConfig(
RestAssuredConfig.config()
.objectMapperConfig(
ObjectMapperConfig.objectMapperConfig()
.jackson2ObjectMapperFactory { _, _ -> mapper }
)
)
.addFilter(
documentationConfiguration(restDocumentationContextProvider)
.operationPreprocessors()
.withResponseDefaults(Preprocessors.removeMatchingHeaders("Date"))
)
.addFilter(defaultDocument)
.addFilter(ResponseLoggingFilter())
.log(LogDetail.ALL)
.build()
}
The important part is adding a new filter (the first one), which takes care of configuring Spring RestDocs to remove the Date from all its snippets, including the contract ones.
How to add custom matchers, using the default SpringCloudContractRestDocs.dslContract(): I don't think it is actually possible right now, but might be wrong here (glad if somebody can chime in and correct me in case)

Spring Webflux: Remove WWW-authenticate header

I am using Spring 5 Webflux with Basic Authentication.
Problem:
When I type a wrong username or password spring reponses with Http Status 401 and includes the www-authenticate: Basic realm="Realm" Http Header which causes the browser to pop up the basic auth box.
How to remove that HTTP Header in Spring 5 Webflux?
Do I have to do a custom Webfilter?
The code below is in Kotlin copied from my project. But the idea can be simply transfered into Java.
So the solution is tied around a custom Webfilter.
#Component
class HttpHeaderWebFilter: WebFilter {
override fun filter(exchange: ServerWebExchange, next: WebFilterChain): Mono<Void> {
return next.filter(exchange).then(Mono.defer {
val headers = exchange.response.headers
if (headers.containsKey("WWW-Authenticate")) {
headers.remove("WWW-Authenticate")
}
Mono.empty<Void>()
})
}
}
We can use the following
if (exchange.getRequest().getHeaders().containsKey("headerKey")) {
exchange.getRequest().mutate().header("headerKey", null, null);
}
We are using the double null, to overcome deprecated Overriding method.
If you are using Spring Framework 5.2, usage of single null is sufficient.
The reason why it sends that header is that the authenticationFailureHandler associated with the default httpBasic() configuration uses the configured entryPoint. If it's not configured, then it uses the default entryPoint (HttpBasicServerAuthenticationEntryPoint) which adds that header to the response.
So, to change this behavior you can set a custom entryPoint, for example:
.httpBasic().authenticationEntryPoint(HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))

How to pass new header to sendRedirect

I feel like this should be easy. I have an app where all I am trying to do is have a form page (index.jsp) that calls a servlet (CheckInfo.java) which sets a new header (myHeader) and redirects the user to another page (redirect.jsp). All of these files are on the same server. The index.jsp is sending the request just fine and CheckInfo is processing and redirecting, but myHeader is not showing up on redirect.jsp. I've read several posts talking about response.sendRedirect sends a 302 which doesn't pass headers and that I should use RequestDispatcher, but nothing seems to work. Is there no way to send headers from a servlet to a jsp?
Here is the servlet code:
response.setHeader("myHeader", "hey there");
response.sendRedirect("redirect.jsp");
I have also tried this:
response.setHeader("myHeader", "hey there");
RequestDispatcher view = request.getRequestDispatcher("redirect.jsp");
view.forward(request, response);
And I have this in redirect.jsp:
System.out.println(request.getHeader("myHeader"));
This does not print anything.
If the answer to my question is no... then I would settle for a way to set the header once I got back to the jsp. My reverse proxy is looking for a specific header to determine whether or not to perform an action. Obviously I tried response.addHeader() on redirect.jsp, but the page has already loaded at that point so that just made me feel dumb.
response.setHeader("myHeader", "hey there");
response.sendRedirect("redirect.jsp");
You are adding it as response header and it is 302 response. Browser on seeing a 302 response will just look for Location header and fire a new request to this location. Custom headers in the response are untouched whereas you are expecting these custom response headers to be included in the request (to new redirect location) which is not being sent.
Solution:-
1. you can use request dispatcher and forward the request instead of external redirect. And you need to use request attributes here.
2. you can call submit form using an ajax request may be jquery like and handle the response manually(for 302 response) but would not suggest you to use this approach as it is not a cleaner and intuitive approach. Just mentioning so that you know there are other ways to achieve this.
The problem is that the redirect() method of the response initiates a new request altogether, thereby loosing the attributes that were set before redirecting. Luckily there is a fluent way of solving the problem still. See below
response.setHeader("myHeader", "hey there");
request.getRequestDispatcher("redirect.jsp").forward(request, response);
Then in your destination you can do response.getHeaders("myHeader")
I have tested the code.
I hope it's clear that in case of asking the client to redirect to another URL - the browser shall not honor the cookies.
However, the 2nd method - where server forwards the request is feasible. The main mistake appears to be in mutating the response while we are supposed to change the request.
Then again, one cannot directly mutate a HttpServletRequest object. Here is one way to do so:
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(request){
public String getHeader(String name) {
String value = super.getHeader(name);
if(Strings.isNullOrEmpty(value)) {
...
value = myNewHeader;
}
return value;
}
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if(values.size()==0) {
...
values.add(myNewHeader);
}
return Collections.enumeration(values);
}
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
names.add(myNewHeaderName);
...
return Collections.enumeration(names);
}
}
Followed by:
RequestDispatcher view = request.getRequestDispatcher("redirect.jsp");
// OR (If you can get servletContext)
RequestDispatcher view = servletContext.getRequestDispatcher("redirect.jsp");
view.forward(requestWrapper, response);
Reference:
https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequestWrapper.html
For the headers case - getHeader(), getHeaders() and getHeaderNames() fn in the reqWrapper obj need Overriding.
Similarly you can override cookies and params.
See also: Modify request parameter with servlet filter
NOTE: It might not be possible to forward a req to an endpoint which expects a different MIME type.
A client side redirect creates a new HTTP request/response pair.
This link may help you more on debugging perspective -
Sending Custom headers

Symfony2: Passing "_format=xml" does not render responce in XML

Strange behavior with passing defaults={"_format" = "xml"} to controller (seemed to work before):
/**
* #Route("/orderxml/{orderguid}", name="_show_order_xml", defaults={"_format" = "xml"})
*/
public function showOrderXML($orderguid)
{
....
$xmloutput = $this->container->get('templating')
->render($templateName, $tpl_data);
$response = new Response($xmloutput);
}
Though I pass defaults={"_format" = "xml"}, the response is still received with content-type=text/html.
Tried to debug the Request - it comes with empty Content-Type header. Attributes of Request do contain _format=xml, but also contain media-type="text/html", which is not familiar to me. As stated in the docs, _format determines the content-type of Request and Response objects.
Currently the only thing I could do is $response->headers->set('Content-Type', 'text/xml');
How can this be fixed?
P.S.: symfony 2.3
_format define the content-type, but you set it only as default. Since the request goes with contant-type html, the default doesn't matter. you have also to set the _format in the requirements to xml only too.

Resources