2
votes

I use apollo-link-error to handle graphql errors. I need to know the uri of the current link in the callback of onError. However, the signature of the callback is as follows:

function ({operation, response, graphQLErrors, networkError, forward})

It seems it is impossible to get the uri from these parameters. So do I miss something? Or do I need to use other tools to achieve this purpose? In fact, I need to know the uri for retrying purpose (retry to request another server).

I config the client as follows,

var uriList = [uri1, uri2]
const customFetch = (uri, options) => {
    const dynamicURI = getOneURIFromURIList()
    return fetch(dynamicURI, options);
}
const errorLink = onError(({ networkError, operation, forward }) => {
    if (needRetry(networkError)) {
        // Here, if I know the URI of the terminating link, I can removed it
        // from the uriList, and customFetch will not choose it again.
        return forward(operation)
    }
})
const link = errorLink.concat(createHttpLink({ fetch: customFetch }))
1
Sharing your client configuration, including the links, would help in providing a more specific answer to your question. Without any sample code, it's hard to provide more than a generalized answer.Daniel Rearden
@DanielRearden Thanks for your kindly reply. I re-edited the question. While the process of re-editing, I found the uri parameter of customFetch may be what I want. Is it the URI of the previous call to getOneURIFromURIList when a retry happens? I haven't try it yet.Neo Li
Unfortunately, after a test, I found the uri parameter of customFetch does not change, it is /graphql as default.Neo Li
Edited my answer with an untested but feasible workaround for what you're trying to do.Daniel Rearden

1 Answers

1
votes

The request URL is not available as a parameter on the onError callback.

Clients only include a single terminating link -- usually an HttpLink or BatchHttpLink. The only exception to this is when we use the split function to support both a WebSocketLink and another terminating link. All that to say your client will generally have a single HttpLink and that link will have a single URL for making requests -- i.e. you'll typically have just the one request URL per client. Unless you're using a custom link or an otherwise atypical setup, given a particular client you should already have access to this URL outside the context of the onError callback.

EDIT:

I would suggest a more traditional approach to load balancing rather than trying to do this client-side (for example, using an actual load balancer or implementing the functionality with nginx). This way, your client would only have one URL to use whether for the initial request or a retry and deciding which server to use for the request would be handled by the backend.

That said, you should be able to achieve what you're trying to do by utilizing context and the split function. Something like this should work:

import { setContext } from 'apollo-link-context'
import { createHttpLink } from 'apollo-link-http'
import { split, from } from 'apollo-link'

const contextLink = setContext(() => ({
  // you could return something other than the URI here, like a number
  // we just need some value that's unique to each link
  targetURI: getOneURIFromURIList()
}))

const optionalHttpLink1 = split(
  (operation) => operation.getContext().targetURI === URI_1,
  createHttpLink({ uri: URI_1 })
)

const optionalHttpLink2 = split(
  (operation) => operation.getContext().targetURI === URI_2,
  createHttpLink({ uri: URI_2 })
)

const optionalHttpLink3 = split(
  (operation) => operation.getContext().targetURI === URI_3,
  createHttpLink({ uri: URI_3 })
)

const errorLink = onError(({ operation }) => {
  // call operation.getContext().targetURI to determine the URI used
})

const link = from([
  contextLink,
  optionalHttpLink1,
  optionalHttpLink2,
  optionalHttpLink3,
  errorLink,
])

You'll also want to make sure you drop the customFetch option for the above to work.

Also note that split takes a second link as an optional third parameter. So with two parameters, you specify the condition and a link to use if the condition is met. With three parameters, you specify a condition, a link to use if the condition is met and a link to use if the condition is not met. All that to say if you're only working with two URIs in the example above, you can use just one split instead of three:

const httpLink = split(
  (operation) => operation.getContext().targetURI === URI_1,
  createHttpLink({ uri: URI_1 }),
  createHttpLink({ uri: URI_2 })
)

If you have more than 2 URIs, you'll want one split per URI.