I'm following this guide, with Zipkin.
I have 3 microservices involed, A -> B -> C, I'm propagating headers from A to B and from B to C.
But in the Zipkin dashboard I only see entries for A -> B and B -> C, not A -> B -> C.
Those are the headers:
[
"x-request-id",
"x-b3-traceid",
"x-b3-spanid",
"x-b3-parentspanid",
"x-b3-sampled",
"x-b3-flags",
"x-ot-span-context"
]
I can see that in B x-b3-parentspanid is null and I guess that's wrong, but the other are working I think...how is it possible?
EDIT:
added code snippets to show headers propagation
A -> B propagation:
app.post("/job", (req, res) => postJob(req.body, req.headers).then((response) => res.send(response)))
...
const postJob = (job, headers) => rp({
method: "POST",
uri: `${API_ENDPOINT}/api/job`,
json: true,
body: job,
headers: Object.keys(headers).filter((key) => TRACING_HEADERS.includes(key)).map((key) => headers[key])
})
B -> C propagation:
#PostMapping("/api/job")
#ResponseBody
fun publish(
#RequestBody job: Job,
#RequestHeader("x-request-id") xreq: String?,
#RequestHeader("x-b3-traceid") xtraceid: String?,
#RequestHeader("x-b3-spanid") xspanid: String?,
#RequestHeader("x-b3-parentspanid") xparentspanid: String?,
#RequestHeader("x-b3-sampled") xsampled: String?,
#RequestHeader("x-b3-flags") xflags: String?,
#RequestHeader("x-ot-span-context") xotspan: String?
): JobResponse = jobsService.publishJob(
job, mapOf(
"x-request-id" to xreq,
"x-b3-traceid" to xtraceid,
"x-b3-spanid" to xspanid,
"x-b3-parentspanid" to xparentspanid,
"x-b3-sampled" to xsampled,
"x-b3-flags" to xflags,
"x-ot-span-context" to xotspan
)
)
...
fun publishJob(job: Job, headers: Map<String, String?>): JobResponse {
val enabled = restTemplate.exchange(
"${gatekeeperConfiguration.endpoint}/",
HttpMethod.GET,
HttpEntity(headers),
EnabledResponse::class.java
).body
if (!enabled!!.isEnabled) // TODO we intentionally want this to crash if body is null
return JobResponse(JobRequestStatus.REJECTED)
return if (this.queue.publish(job)) JobResponse(JobRequestStatus.OK)
else throw RuntimeException("I don't know what to do, yet")
}
Object.keys(headers).filter((key) => TRACING_HEADERS.includes(key)).map((key) => headers[key]) returns an array.
What you want is:
Object.keys(headers)
.filter(key => TRACING_HEADERS.includes(key))
.reduce((obj, key) => {
obj[key] = headers[key];
return obj;
}, {})
I'm pretty sure this isn't an istio / distributed tracing issue ;-)
b3-propagation of x-b3-parentspanid (https://github.com/openzipkin/b3-propagation) can be configured in your application.yml by adding:
opentracing:
jaeger:
enable-b3-propagation: true
Related
I have the following problem: I have a Angular 2 application which sends a get request like this:
getStatus(cb:(boolean, error) => void){
this.http.get(this.uri+'/forms/status')
.subscribe(
(res: Response) =>{
console.dir(res);
this.response = res;
if(res.status === 200)cb(true, null);
else cb(false, "No connection established");
}
)
}
So this method should check,if my service is online or not and should send a message to the user, if it is offline. My problem is that I always will get
Failed to load resource: net::ERR_CONNECTION_RESET
when I call the method.
My question is how I can handle it that the method just returns the boolean as false, when my service is offline.
Best regards.
Switching to
getStatus(cb:(boolean, error) => void){
this.http.get(this.uri+'/forms/status')
.map(val => true)
.catch(err => Observable.of([false])
.subscribe(
(res: boolean) => cb(res, res ? null : "No connection established");)
}
returns the error message:
ERROR in [default] C:\Development\Code\formcreator-ui\app\src\service\form.servi
ce.ts:66:8
Argument of type '(res: boolean) => void' is not assignable to parameter of type
'NextObserver<boolean[]> | ErrorObserver<boolean[]> | CompletionObserver<boolea
n[]> | ((value: boo...'.
Type '(res: boolean) => void' is not assignable to type '(value: boolean[]) =>
void'.
Types of parameters 'res' and 'value' are incompatible.
Type 'boolean[]' is not assignable to type 'boolean'.
If you mean to suppress the error message in the browser console, then you're out of luck. This error is created by the browser and there is no way avoiding it.
Otherwise this should do what you want.
getStatus(cb:(boolean, error) => void){
this.http.get(this.uri+'/forms/status')
.map(val => true)
.catch(err => Observable.of([false])
.subscribe(
(res: Response) => cb(res, res ? null : "No connection established");
)
}
but instead of cb I would do it like
getStatus(){
return this.http.get(this.uri+'/forms/status')
.map(val => true);
.catch(err => Observable.of([false])
}
then it can be used like
this.getStatus().subscribe(avail => avail ? doSomething() : console.log("No connection"));
Observables are to avoid callback hell, therefore using this feature is preferred instead of passing callbacks around.
The following coffeeScript is running properly without the references to setUpdateInterval.
class Notifications
constructor: ->
#notifications = $("[data-behavior='notifications']")
#setup() if #notifications.length > 0
setUpdateInterval()
setup: ->
$.ajax(
url: "/notifications.json"
dataType: "JSON"
method: "GET"
success: #handleSuccess
)
handleSuccess: (data) =>
items = $.map data, (notification) ->
"<li class='active'><a href='#{notification.url}'>#{notification.actor} #{notification.notifiable.type}</a></li>"
$("[data-behavior='unread-count']").text(items.length)
$("[data-behavior='notification-items']").html(items)
setUpdateInterval: (notifications) ->
callback = #setup.bind(this)
setInterval( callback, 15000 )
jQuery ->
new Notifications
What is incorrect with the additional line and bloc inserted?
Based on comment, the following functions.
constructor: ->
#notifications = $("[data-behavior='notifications']")
#setUpdateInterval()
#setup() if #notifications.length > 0
setup: ->
$.ajax(
url: "/notifications.json"
dataType: "JSON"
method: "GET"
success: #handleSuccess
)
setUpdateInterval: (notifications) ->
callback = #setup.bind(this)
setInterval( callback, 15000 )
I've got a small react app I'm playing with, just go get the hang of the library. The app is just a series of lists, which are populated from a server. When a list item is clicked, the value of that item is added to a list filters at the app level, which will then be used to call new data to populate the lists.
The problem is that I can't seem to get my lists to reconcile with the new data from the app (parent), even when calling setState. Here's my code (coffee):
###
#jsx React.DOM
###
{div, h1, h2, h4, ul, li, form, input, br, p, strong, span, a} = React.DOM
SearchApp = React.createClass
handleTopItemClick: (filter) ->
facet = filter.field
filters = #state.filters
if filters.facets[facet] and filters.facets[facet].length > 0
filters.facets[facet].push filter.value
else
filters.facets[facet] = [filter.value]
strArr = []
_.each filters.facets, (valArr, field) ->
_.each valArr, (val) ->
strArr.push "+(#{field}:\"#{val}\")"
#setState
filters: filters
queryStr: strArr.join(' ').trim()
getInitialState: ->
filters:
facets: {}
queryStr: ''
render: ->
(div {
id: 'content'
className: "search-wrap"
},
(h1 {}, "Search")
(div
id: 'widgets',
(TopList
title: 'Top Domains'
params:
query: #state.queryStr
field: 'domain'
onItemClick: #handleTopItemClick
)
(TopList
title: 'Top Senders'
params:
query: #state.queryStr
field: 'from'
onItemClick: #handleTopItemClick
)
(TopList
title: 'Top Recipient'
params:
query: #state.queryStr
field: 'recipient'
onItemClick: #handleTopItemClick
)
)
)
TopItem = React.createClass
getDefaultProps: ->
value: ''
count: 0
field: null
render: ->
(li {},
(a {
onClick: #handleClick
className: 'top-item-filter'
title: #props.value
},
(strong {}, #props.value)
(span {}, #props.count)
)
)
handleClick: (event) ->
event.preventDefault()
#props.onItemClick #props.value
TopList = React.createClass
getInitialState: ->
data: []
params: #props.params
componentWillReceiveProps: (nextProps) ->
#setState params: nextProps.params
componentWillMount: ->
request.post("/facet").send(#state.params).end (results) =>
#setState data: JSON.parse(results.text)
render: ->
itemNodes = _.map #state.data, (item) =>
key = item.value
TopItem
value: item.value
count: item.count
key: key
onItemClick: #handleItemClick
(div {className: 'widget top-item'},
(h2 {className: 'widget-header'}, "#{#props.title}")
(ul {className: 'top-items-list'}, itemNodes)
)
handleItemClick: (value) ->
#props.onItemClick
value: value
field: #props.params.field
React.renderComponent SearchApp(null), document.getElementById("content")
The lists all render fine the first time around, fetching the unfiltered data as expected. When I click on a list item, the SearchApp receives the event, and updates its own state accordingly. componentWillReceiveProps is properly called on the TopList classes, but the setState call there doesn't update their state, and thus, they aren't reconciling. I've verified that nextProps contains the new information. What am I missing?
There isn't really any reason to save the props in state; and it's much less error prone to keep props as the source of truth. It also simplifies the code a bit.
For the actual issue, though; componentWillMount is only called once here. If you want to repeat the AJAX request when new params are passed; you can do that like so:
TopList = React.createClass
getInitialState: ->
data: []
getSearchResultsFromServer: (params) ->
request.post("/facet").send(params).end (results) =>
if #isMounted()
#setState data: JSON.parse(results.text)
componentWillReceiveProps: (nextProps) -> #getSearchResultsFromServer nextProps.params
componentDidMount: -> #getSearchResultsFromServer #props.params
render: ->
itemNodes = _.map #state.data, (item) =>
key = item.value
TopItem
value: item.value
count: item.count
key: key
onItemClick: #handleItemClick
(div {className: 'widget top-item'},
(h2 {className: 'widget-header'}, "#{#props.title}")
(ul {className: 'top-items-list'}, itemNodes)
)
handleItemClick: (value) ->
#props.onItemClick
value: value
field: #props.params.field
Ideally, your ajax library would support aborting requests; and in componentWillUnmount you abort those requests.
The response is 415 (Unsupported Media Type) .
Client Side Code:
$.ajax({
url: "/book",
//contentType: 'application/json',
data: {action: "hello", method: "json"},
dataType: "json",
type: "POST",
complete: function(a, b) {
console.log(a);
console.log(b);
}
});
Server Side Code:
content_types_provided(Req, State) ->
{[
{<<"application/json">>, handle_to_all}
], Req, State}.
handle_to_all(Req, State) ->
Body = <<"{\"rest\": \"Hello World!\"}">>,
{Body, Req, State}.
If I update the type from "POST" to "GET" from client side, everything is okay.
Anything I missed?
content_types_provided method of cowboy accepts only GET and HEAD
Go through the following link and change the code accordingly
https://ninenines.eu/docs/en/cowboy/1.0/manual/cowboy_rest/
You can use cowboy_rest, implement the content_types_accepted/2 callback method like so:
content_types_accepted(Req, State) ->
case cowboy_req:method(Req) of
{<<"POST">>, _ } ->
Accepted = {[{<<"application/json">>, put_json}], Req, State};
{<<"PUT">>, _ } ->
Accepted = {[{<<"application/json">>, post_json}], Req, State}
end,
Accepted.
I think this way you can have separate handlers for different HTTP Verbs/Methods. This gives you cleaner code too :)
And the various handlers:
%% handle http put requests
put_file(Req, State) ->
{true, Req, State}.
%% handle http post requests
post_json(Req, State) ->
{true, Req, State}.
I can't seem to get the POST parameters to come through from an AJAX javascript POST. The error is:
#6angl7689 - Internal server error, for request [POST /myRoute] ->
play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[NoSuchElementException: None.get]]
Route:
POST /myRoute controllers.Application.testPost
Controller code:
def myForm = Form(
tuple(
"valOne" -> text,
"valTwo" -> text))
def testPost() = Action { implicit request =>
val (valOne, valTwo) = myForm.bindFromRequest.get // Errors here
println("valOne: " + valOne)
println("valTwo: " + valTwo)
Ok
}
CoffeeScript client-side:
params =
valOne: 'valOne'
valTwo: 'valTwo'
$.ajax
type: 'POST'
url: '/myRoute'
data: params
Update your client code,
$.ajax(
type: 'POST'
url: '/myRoute'
data: {valOne: 'valOne', valTwo: 'valTwo'}
);
This should work