Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: recover from failed HTTP requests to third party gateways #783

Merged
merged 9 commits into from
Oct 17, 2019
Merged

feat: recover from failed HTTP requests to third party gateways #783

merged 9 commits into from
Oct 17, 2019

Conversation

colinfruit
Copy link
Contributor

@colinfruit colinfruit commented Oct 6, 2019

This PR closes #640, enabling dead public gateways to be redirected to the default public gateway. Added as an experimental option, defaulting to false.

}
// if error cannot be recovered via DNSLink
// direct the request to the public gateway
const recoverableViaPubGw = isRecoverableViaPubGw(request, state, ipfsPathValidator)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including the redirect functionality was necessary in both onCompleted and onErrorOccurred, because of the different types of gateway failures. Many requests result in failure, others return 4xx or 5xx error codes.

@colinfruit colinfruit changed the title #640 - Recover dead public gateway links WIP feat: Recover dead public gateway links Oct 6, 2019
@colinfruit colinfruit changed the title WIP feat: Recover dead public gateway links [WIP] feat: Recover dead public gateway links Oct 7, 2019
@lidel
Copy link
Member

lidel commented Oct 11, 2019

Thank you for opening this @colinfruit!
Is it still wip, or ok to review?

@colinfruit
Copy link
Contributor Author

Hi @lidel, the basic structure is fine to review. I'm working on tests, finding a clean way to stub promises (browser.tabs.query/create), and reviewing the error codes that were added, to make sure I have all that we want, and none that we don't want. Glad to hear your comments!

@colinfruit
Copy link
Contributor Author

colinfruit commented Oct 12, 2019

To elaborate a little more on the testing part of my work, I've been going through a couple of different libraries to see if any of them can promisify sinon-chrome. The only two that could theoretically work are then-chrome and chrome-promise. chrome-promise is no longer maintained, just bug fixes, and then-chrome is outdated and seemingly abandoned. The only other option afaik is stubbing the needed APIs manually. Has this been discussed before for this project, and is there a better way to accomplish this?

@lidel
Copy link
Member

lidel commented Oct 14, 2019

@colinfruit I stumbled upon similar problem with testing WebExtension APIs before. On top of the problem with abandoned libraries, sometimes Chromium and Firefox have slight changes in some APIs, making a generic library not feasible. In redirect tests I ended up writing bare minimim input-output/side-effect tests for modifyRequest.* methods with hardcoded input specific to Chromium and Firefox, to ensure we have regression tests for each vendor (example).

I think we should be fine with adding test/functional/lib/ipfs-request-gateway-recover.test.js with similar tests for modifyRequest.onCompleted and modifyRequest.onErrorOccurred (modelled after ipfs-request-gateway-redirect.test.js). If there are no vendor-specific APIs in onCompleted and onErrorOccurred, a single set of tests for both Firefox and Chrome will be enough.

Let me know if you got any questions, will be glad to help if I can :)

@colinfruit
Copy link
Contributor Author

colinfruit commented Oct 14, 2019

@lidel, that sounds good, except for the fact that it requires reworking the way the onErrorOccurred originally was written. The current setup doesn't return anything, so in my current test setup I am checking that browser.create was called with the expected arguments,

it('should redirect broken non-default public gateway IPFS request to public gateway', async function () {
      const request = url2request('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
      await requestHandler.onCompleted(request)
      assert.ok(browser.tabs.create.withArgs({ url: 'https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', active: true, openerTabId: 12345 }).calledOnce, 'tabs.create should be called with IPFS default public gateway URL')
    })

I was under the impression that it would be bad practice to add a return value solely for the purposes of testing, but I'm happy to add them if you're OK with that?

@colinfruit
Copy link
Contributor Author

@lidel just added my tests as a working example so it's easier to see what I am doing, will remove them if your previous comment is what you want. :)


before(function () {
global.URL = URL
browser.tabs = { ...browser.tabs, query: sinon.stub().resolves([{ id: 20 }]) }
Copy link
Contributor Author

@colinfruit colinfruit Oct 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise this query will throw an error.

Copy link
Member

@lidel lidel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thank you @colinfruit!

The current setup doesn't return anything, so in my current test setup I am checking that browser.create was called with the expected arguments

The way you wrote tests is absolutely correct. Most of request APIs are input-output based, but ones related to error recovery need to produce side-effect (new tab). All good.


A few final asks before we merge this:

  • Let's frame language and names around "recovering from failed HTTP requests". Public gateway is a way of recovering, but its just an implementation detail that may change in the future while most of this code remains. This means we want to:
    • rename recoverViaPublicGateway (config option) to something like recoverFailedRequests
    • rename funcitons and variables from recoverableViaPubGw to recoverable
  • Would it be possible to recover from broken subdomain gateway links?

add-on/_locales/en/messages.json Outdated Show resolved Hide resolved
add-on/src/lib/options.js Outdated Show resolved Hide resolved
add-on/_locales/en/messages.json Outdated Show resolved Hide resolved
recoverableErrorCodes.has(request.statusCode)
if (recoverableViaPubGw) {
const redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
const redirect = { redirectUrl }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we drop redirect and use redirectUrl directly?

Copy link
Contributor Author

@colinfruit colinfruit Oct 17, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is the cleanest way to do this. Without it we would have to worry about the dnslink redirect, and do something like const redirectUrl = redirect ? redirect.redirectUrl : null to be able to use this with the same createTabWithURL function.

add-on/src/lib/ipfs-request.js Outdated Show resolved Hide resolved
add-on/src/lib/ipfs-request.js Outdated Show resolved Hide resolved
test/functional/lib/ipfs-request-gateway-recover.test.js Outdated Show resolved Hide resolved
test/functional/lib/ipfs-request-gateway-recover.test.js Outdated Show resolved Hide resolved
test/functional/lib/ipfs-request-gateway-recover.test.js Outdated Show resolved Hide resolved
@colinfruit
Copy link
Contributor Author

thanks for the review, @lidel, resolved most of your suggestions. It would be cool to recover broken sub-domains. Maybe a separate issue to track this would be good? I'm glad to take a look at it if I have some time.

@colinfruit colinfruit changed the title [WIP] feat: Recover dead public gateway links feat: Recover dead public gateway links Oct 17, 2019
Copy link
Member

@lidel lidel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! LGTM, excited about this!
I'll fill a new issue for subdomains shortly.

@lidel lidel changed the title feat: Recover dead public gateway links feat: recover from failed HTTP requests to third party gateways Oct 17, 2019
@lidel lidel merged commit 614da95 into ipfs:master Oct 17, 2019
lidel added a commit that referenced this pull request Oct 17, 2019
This change builds on top of #783 and:

-  adds support for recovery from DNS lookup failures
-  enables recovery for all HTTP Codes >= 400 
-  recovers `.eth` DNS failures bu reopening website on EthDNS gateway at `.eth.link`
-  simplifies some code paths and adds more tests

Motivation:

When a third-party IPFS gateway is discontinued or censored at DNS level, the IPFS Companion should retry request using currently active gateway set by the user (public or local).

We also want to recover in situation when website with DNSLink has a valid DNS `TXT` record, but HTTP behind `A` record is down or unreachable.

*Note*: right now the only real use for DNS recovery is support of .eth TLD via .eth.link gateway, however in the future this could provide means of working around DNS-based censorship (eg. by executing DNSLink lookups over libp2p as a fallback).

closes #678, closes #640
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Recover from dead Public Gateways
2 participants