HATEOAS paths are invalid when using an API Gateway in a Spring Boot app - spring

I have two spring boot applications where one of them is acting as an API Gateway (as discussed here Spring Example). The other which is wired into the first one is exposing a profile service using spring-data-rest (spring-data-neo4j-rest).
The first application is starting on port 8080 and is using zuul to route requests to the second as follows:
zuul:
routes:
profiles:
path: /profiles/**
url: http://localhost:8083/profiles/
This all works fine and requests to http://localhost:8080/profiles are being served from the second app. The problem though is that the HATEOAS links in the response are incorrect. The response from calling that second service are correct:
{
"_links": {
"self": {
"href": "http://localhost:8083/profiles{?page,size,sort}",
"templated": true
},
"search": {
"href": "http://localhost:8083/profiles/search"
}
},
"_embedded": {
"profiles": [
{
"name": "Andrew Rutter",
"_links": {
"self": {
"href": "http://localhost:8083/profiles/0"
}
}
},
{
"name": "Andrew Rutter",
"_links": {
"self": {
"href": "http://localhost:8083/profiles/1"
}
}
}
]
},
"page": {
"size": 20,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
But when this comes back to my API Gateway, the links are being rewritten to
{
"name": "Andrew Rutter",
"_links": {
"self": {
"href": "http://localhost:8080/profiles/profiles/0"
}
}
}
Which is the gateway path alias plus the actual service base Uri. Am I missing a zuul option to disable that behavior and just leave the hateoas uri in place with a host adjustment. Or is there a way for my service behind the gateway to be wired to / rather then the default resource endpoint of /profiles (in this case) which would avoid the undesirable path being added in.
Thanks!

Zuul or Spring-Cloud adds the "X-Forwarded-Host" header to all the forwarded requests, which Spring-hateoas respects and modifies the links appropriately. To quote from Spring-Cloud docs:
The X-Forwarded-Host header added to the forwarded requests by
default. To turn it off set zuul.addProxyHeaders = false. The prefix
path is stripped by default, and the request to the backend picks up a
header "X-Forwarded-Prefix" ("/myusers" in the examples above).
You can try the recommended fix, which is to set the zuul.addProxyHeaders=false

I had exactly the same problem. Change your config as follows:
zuul:
routes:
profiles:
path: /profiles/**
url: http://localhost:8083
stripPrefix: false
This routes all requests going to the gateway matching "/profiles/**" to your back end server "http://localhost:8083" and leaves the prefix (in your case "/profiles" since that's what matched the route).

Zuul forwards to the /profiles contextPath.
Try setting this as configuration:
zuul:
routes:
profiles:
path: /profiles/**
url: http://localhost:8083/

After struggling some time with the same problem, finally I've tried zuul.addProxyHeaders = true and it works!
Links are not broken anymore.

In the demo app I used for SpringOne in my talk about Spring Data REST, I have the following configuration to both handle URI rewrites as well as adjust prefix headers set properly.
zuul:
routes:
api:
path: /api/**
serviceId: spring-a-gram-backend
stripPrefix: false
files:
path: /files/**
serviceId: spring-a-gram-mongodb-fileservice
stripPrefix: false
See it in full at https://github.com/gregturn/spring-a-gram/blob/master/spring-a-gram-frontend/src/main/resources/application.yml#L23-L32

Related

Why is the "readinessState" detail of /health different from the readiness probe status?

In Spring-Boot 2.4, I have this problem with the Actuator health endpoint and readiness probe. When one of my custom key components is down, the /health/readiness endpoint says DOWN and the /health endpoint too, but the readinessState detail of /health still says UP.
Why is it that way? Shouldn't readinessState say DOWN too?
None of the many tutorials I found online seem to address this question.
My hypothesis: the readinessState has nothing to do with readiness and exposes another piece of information. I hope I'm wrong, because it would be nonesense and what I understand of the code seems to indicate otherwise.
More about my configuration:
Relevant excerpt from application.yml
management:
endpoints:
web:
base-path: /
endpoint:
health:
show-details: ALWAYS
probes:
enabled: true
group:
readiness:
include: db, myCustom, diskSpace
And when I make myCustom go DOWN, following results appear:
GET /health
{
"status": "DOWN",
"components": {
..., // other components
"myCustom": {
"status": "DOWN"
},
"readinessState": {
"status": "UP" // here UP
}
},
"groups": [
"liveness",
"readiness"
]
}
GET /health/readiness
{
"status": "DOWN", // but here DOWN
"components": {
..., // other components
"myCustom": {
"status": "DOWN"
}
}
}
The readiness state only monitors specific health groups. It needs to be told about your custom component.
By default, Spring Boot does not add other Health Indicators to these groups.
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready-kubernetes-probes-external-state

How to import actuator httptrace in actuator prometheus? (actuator, spring boot, grafana)

Imagine this is my http://localhost:8080/actuator ouotput:
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"prometheus": {
"href": "http://localhost:8080/actuator/prometheus",
"templated": false
},
"httptrace": {
"href": "http://localhost:8080/actuator/httptrace",
"templated": false
}
}
}
Now I've hooked up my prometheus environment to /actuator/prometheus and that works fine. I als want prometheus to read my httptrace so I also added /actuator/httptrace to my prometheus config. However this does not work. The formatting on the httptrace endpoint is in json and the formatting in prometheus is in yaml, I think I need the httptrace in the prometheus yaml. Prometheus eats the yaml just fine, the json not so much.
How can I pass my httptrace to actuator/prometheus from my spring boot project? In the end my goal is to get the timeTaken value for every request in grafana.
Spring's HttpTraceRepository exposes the recent traces on an endpoint but not in prometheus as percentiles. So I can suggest two paths:
You implement your own HttpTraceRepository that wraps the one your using (default is InMemory....) and then override the method to fire off some custom Timer from [io.micrometer.core] with the timing (https://micrometer.io/docs/concepts#_timers) which will get aggregated as percentiles if you also enable via https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.metrics.customizing.per-meter-properties
In the end my goal is to get the timeTaken value for every request in grafana.
Just use http_server_requests_seconds_* that are captured per endpoint (not request)
http_server_requests_seconds_count
is the total number of requests your application received at this endpoint
http_server_requests_seconds_sum
is the sum of the the duration of every request your application received at this endpoint
and this great post explains how to use it in prometheus, https://tomgregory.com/spring-boot-default-metrics/

krakend api gateway panic: "X in new path conflicts with existing wildcard Y in existing prefix Z"

I have two webservices, and I'd like to manage both endpoints separated by the prefix using krakend API gateway.
Below is my configuration:
{
"version": 2,
"name": "My API Gateway",
"port": 8080,
"host": [],
"endpoints": [
{
"endpoint": "/api/entity/{entityID}",
"output_encoding": "no-op",
"method": "POST",
"backend": [
{
"url_pattern": "/api/entity/{entityID}",
"encoding": "no-op",
"host": [
"http://987.654.32.1"
]
}
]
},
{
"endpoint": "/api/entity/member/assign/{userID}",
"output_encoding": "no-op",
"method": "GET",
"backend": [
{
"url_pattern": "/api/entity/member/assign/{userID}",
"encoding": "no-op",
"host": [
"http://123.456.789.0"
]
}
]
}
]
}
when I run it, error occurs:
panic: 'member' in new path '/api/entity/member/assign/:userID' conflicts with existing wildcard ':entityID' in existing prefix '/api/entity/:entityID'
As far as I understand, it seems the {entityID} on the first endpoint is conflicting with /member/ in the second endpoint. Is this error expected behaviour or is there any problem with my configuration file?
This is a known limitation of the Gin library that KrakenD uses internally, you can reproduce this behavior directly in the library with this go code, which will reproduce exactly the same issue:
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.New()
r.GET("/ping", handler)
r.GET("/ping/foo", handler)
r.GET("/ping/:a", handler)
r.GET("/ping/:a/bar", handler)
}
func handler(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
}
See the code in this issue.
The solution is to declare endpoint paths that are not colliding subsets of other endpoints. In your configuration the endpoint /api/entity/member/assign/{userID} is a subset of /api/entity/{entityID}.
Notice that {placeholders} are like using wildcards, so your first endpoint could be expressed in other systems like /api/entity/*, and therefore /api/entity/member/assign/{userID} is a positive match.
Any slight change in your configuration where the wildcard does not collide will fix this situation. As an example, the following two endpoints would work for you:
/api/entity/find/{entityID}
/api/entity/member/assign/{userID}
Thanks to #alo on explaining this issue.
I have come across same one, as i have krakend endpoints in following manner:
GET: /v1/projects/{project_uuid}
GET: /v1/projects/{project_key}/portfolio
But surprisingly, by tricking krakenD like this worked fine.
GET: /v1/projects/{key} // In swagger docs mentioned this key to be supplied as uuid
GET: /v1/projects/{key}/portfolio // In swagger docs mentioned this key to be supplied as string
For now this endpoints triggering my backend client as expected. hopefully this annoying thing gets fixed.

Spring Boot Zuul hateoas REST response has direct service links in resource

I have Zuul + Eureka + Spring Boot Service Endpoint + Hateoas response configuration. When I access the service through Zuul Gateway, the resource links in the response are direct links to the service endpoints, shouldn't they be the Gateway links? What am i missing here?
Gateway Endpoint : http://localhost:8762/catalog/products/10001
Direct Service Endpoint : http://localhost:8100/products/10001
application.properties for Zuul
spring.application.name=zuul-server
eureka.client.service-url.default-zone=http://localhost:8761/eureka/
# Map paths to services
zuul.routes.catalog-service=/catalog/**
zuul.addProxyHeaders=true
Actual Response on Gateway Endpoint : http://localhost:8762/catalog/products/10001
{
"title" : "The Title",
"description" : "The Description",
"brand" : "SOME BRAND",
"price" : 100,
"color" : "Black",
"_links" : {
"self" : {
"href" : "http://localhost:8100/products/10001"
}
}
}
Expected Response should have Gateway URL in href
{
"title" : "The Title",
"description" : "The Description",
"brand" : "SOME BRAND",
"price" : 100,
"color" : "Black",
"_links" : {
"self" : {
"href" : "http://localhost:8762/catalog/products/10001"
}
}
}
I've got this issue and resolved it via this post on github
The gist is
spring-boot <=2.1
#Bean
ForwardedHeaderFilter forwardedHeaderFilter() {
return new ForwardedHeaderFilter();
}
spring-boot >= 2.2
server.use-forward-headers=true
While #Nikhil said he fixed by just adding #Bean, in my case is just the opposite:
I just added forward-headers-strategy: FRAMEWORK (currently, server.use-forward-headers is deprecated) and it worked for me that way.
Thank you #Zipster!
Additional info:
Property server.use-forward-headers accepts three possible values:
NONE
NATIVE
FRAMEWORK
I tested the three options to check the differences.
My Zuul gateway (port 8080) routes are configured as it follows:
zuul:
prefix: /api/v0
sensitive-headers: Cookie,Set-Cookie # Allow Authorization header forwarding
routes:
api-v0-questions:
path: /questions/**
service-id: api-v0-questions
strip-prefix: false
NATIVE - URL points to gateway and stripping the /api/v0 (top prefix):
"_links": {
"self": {
"href": "http://localhost:8080/questions/5f6fa0300ec87b34b70393ca"
}
FRAMEWORK - URL points to gateway and DOES NOT strip the /api/v0 prefix:
"_links": {
"self": {
"href": "http://localhost:8080/api/v0/questions/5f6fa0300ec87b34b70393ca"
}
NONE - URL points to service, just like as adding no property at all:
"_links": {
"self": {
"href": "http://localhost:8081/questions/5f6f96ba0ec87b34b70393b2"
}

Spring Boot Actuator endpoint configuration doesn't seem to be working as expected

I have a very simple spring boot application. It is just a zuul reverse proxy. there is no security or anything other than basic settings to discover our services via eureka and path mapping on a per service basis. I'm trying to prevent our actuator endpoints from being publicly exposed but still want the health check endpoint to be used for our ELB but want do not want it to report on the health of all the services it is aware of (i want it to be sensitive). While trying to figure out what properties i need to set to get the expected behavior, i am experiencing very unexpected behavior.
For example, when i set the property endpoints.sensitive=true, this DOES NOT change the default value of the health check endpoint to be sensitive. This seems to go against what the documentation says.
http://docs.spring.io/spring-boot/docs/1.4.2.RELEASE/reference/htmlsingle/#production-ready-customizing-endpoints
Likewise, you can also choose to globally set the “sensitive” flag of
all endpoints. By default, the sensitive flag depends on the type of
endpoint (see the table above). For example, to mark all endpoints as
sensitive except info:
endpoints.sensitive=true
endpoints.info.sensitive=false
In fact, when running in debug, i never see the org.springframework.boot.actuate.endpoint.EndpointProperties#isSensitive get called.
To get health endpoint to be sensitive, i need to explicitly set the property endpoints.health.sensitive=true. Oddly, when this setting is provided, now org.springframework.boot.actuate.endpoint.EndpointProperties#isSensitive gets called.
So this is great, my health check endpoint is now just reporting UP or DOWN and nothing else. But now I want the health check endpoint to be the ONLY endpoint enabled. So i set endpoints.enabled=false and endpoints.health.enabled=true which should disable all the endpoints except health. However, this does not seem to be the case. In my instance, I am able to hit /routes, /resume, /pause, /hystrix.stream, and others. I was only able to determine this when i disabled all endpoints with endpoints.enabled=false and then enabled the actuator endpoint with endpoints.actuator.enabled=true and that allowed me to hit the actuator endpoint which then reported that these endpoints were enabled.
{
"links": [
{
"rel": "self",
"href": "http://localhost:9200/actuator"
},
{
"rel": "resume",
"href": "http://localhost:9200/resume"
},
{
"rel": "pause",
"href": "http://localhost:9200/pause"
},
{
"rel": "hystrix.stream",
"href": "http://localhost:9200/hystrix.stream"
},
{
"rel": "env",
"href": "http://localhost:9200/env"
},
{
"rel": "routes",
"href": "http://localhost:9200/routes"
},
{
"rel": "health",
"href": "http://localhost:9200/health"
},
{
"rel": "refresh",
"href": "http://localhost:9200/refresh"
},
{
"rel": "restart",
"href": "http://localhost:9200/restart"
}
]
}
I would have expected to ONLY see the two endpoints I explicitly enabled.
{
"links": [
{
"rel": "self",
"href": "http://localhost:9200/actuator"
},
{
"rel": "health",
"href": "http://localhost:9200/health"
}
]
}
disabling each endpoint individually does not seem remove them from the actuator endpoint but now when attempted to access, i get a "This endpoint is disabled" message which is an improvement. I however don't seem to be able to disable the routes or `hystrix.stream* endpoints as there seems to be no configuration that exposes this ability.
All this said, I am wondering if this is the expected behavior or is this a bug?
I ran into the same problem as you described here. Please check your spring boot version first! There was a bug that global 'endpoints.sensitive' settings did NOT take effect at some specified spring boot version. (Not sure about the version number exactly. It seems a refactor regression in spring boot.)
Here are some references.
Allow global sensitive override #4419
Spring Boot Actuator: setting all endpoints as sensitive makes all accessible #4368
After I updated my spring boot to version 1.3.0 RELEASE, the setting 'endpoints.sensitive = true' works for me correctly. Hopes it will also work for you. Good luck, man.

Resources