apollographql / apollo-link

:link: Interface for fetching and modifying control flow of GraphQL requests
https://www.apollographql.com/docs/link/
MIT License
1.44k stars 344 forks source link

[Feature] apollo-link-http GET method remove ignored characters #1079

Open SaschaDens opened 5 years ago

SaschaDens commented 5 years ago

For a project that I'm working on we're making use of the GET method to make the call benefit of HTTP caching. This is working successfully but we do notice that the query parameter contains characters which are not necessary for executing the query.

Example Below an example were we are using graphql-tag to parse the query. Newlines are included for readability of the query.

import gql from 'graphql-tag';

const HERO_QUERY = gql`
  query HERO_QUERY {
    hero {
      name
      friends {
        name
      }
    }
  }
`;

The actual behaviour results in the following HTTP request:

GET /graphql?query=query%20HERO_QUERY%20%7B%0A%20%20hero%20%7B%0A%20%20%20%20name%0A%20%20%20%20friends%20%7B%0A%20%20%20%20%20%20name%0A%20%20%20%20%20%20__typename%0A%20%20%20%20%7D%0A%20%20%20%20__typename%0A%20%20%7D%0A%7D%0A&operationName=HERO_QUERY&variables=%7B%7D HTTP/1.1
Host: localhost:3000
Connection: keep-alive
content-type: application/json

Expected behaviour is a GET query without unnecessary characters:

GET /graphql?query=query%20HERO_QUERY%7Bhero%7Bname%20friends%7Bname%7D%7D%7D&operationName=HERO_QUERY&variables=%7B%7D HTTP/1.1
Host: localhost:3000
Connection: keep-alive
content-type: application/json

I currently don't directly see a way/ability to implement a workaround to achieve the same result. If this request make sense I can provide a PR for this to do the necessary modifications.

https://github.com/apollographql/apollo-link/blob/951217bad34c91f89c1f167cc809bd0cc18fd922/packages/apollo-link-http/src/httpLink.ts#L198-L203

The body of rewriteURIForGET can be stripped before going through the encodeURIComponent.

It would be possible to reuse the logic that is used in graphql package where there is a utility method available stripIgnoredCharacters.

The package graphql is already referenced in apollo-link-http and can be reused or custom logic can be directly included to sanitize the query.

SaschaDens commented 5 years ago

I’ve tried to do the modification outside the apollo-link-http library by creating a custom ApolloLink extension to strip the query before it gets passed towards createHttpLink but with no luck unfortunately.

import { ApolloLink } from 'apollo-link';
import gql from 'graphql-tag';
import { print } from 'graphql/language/printer';
import { stripIgnoredCharacters } from 'graphql/utilities/stripIgnoredCharacters';

class CreateShortQueryLink extends ApolloLink {
  request(operation, forward) {
    const strippedQuery = stripIgnoredCharacters(print(operation.query));

    const request = {
      ...operation,
      getContext: operation.getContext,
      setContext: operation.setContext,
      query: gql(strippedQuery),
    };

    return forward(request);
  }
}

const link = ApolloLink.from([
  new CreateShortQueryLink(),
  createHttpLink({ uri: 'http://localhost:3000/graphql' }),
]);

It seems that the print of graphql/language/printer is adding again the unnecessary characters.

print(gql('query Posts{posts(orderBy:id_DESC){id title __typename}}'))

// outputs
query Posts{posts(orderBy:id_DESC){id title __typename}} query Posts {
  posts(orderBy: id_DESC) {
    id
    title
    __typename
  }
}

This same method print method seems to be also used by apollo-link-http-common which is used in apollo-link-http to create the GET query.

https://github.com/apollographql/apollo-link/blob/c32e170b72ae1a94cea1c633f977d2dbfcada0e1/packages/apollo-link-http-common/src/index.ts#L239

intellix commented 5 years ago

Also tried the above with something like:

export const createMinifyLink = () => {
  return new ApolloLink((operation: Operation, forward: NextLink) => {
    const stripped = stripIgnoredCharacters(operation.query.loc!.source);
    return forward({
      ...operation,
      getContext: operation.getContext,
      setContext: operation.setContext,
      query: gql(stripped),
    });
  });
};

But get a load of warnings about re-using fragment names and problems writing to store.

I would use an HTTP Interceptor but then you've got issues with differing hashes when using APQ. I've got queries that are 44kb and full of %20%20%20. Think if I can strip the ignored characters it'd probably half the size.

You can't even pre-process it because ApolloClient just adds __typename throughout and re-prettifies it:

export function stripIgnoredCharactersGql(literals: string[], ...placeholders: any[]) {
  const strippedLiterals = literals.map(str => stripIgnoredCharacters(str));
  return gql(strippedLiterals, ...placeholders);
}

export const configQuery = stripIgnoredCharactersGql`
  query {
    hello {
      world
    }
  }
`;
biomancer commented 5 years ago

We've ended up patching apollo-link-http-common via https://www.npmjs.com/package/patch-package to use stripIgnoredCharacters right after printer was used:

patches/apollo-link-http-common+0.2.13.patch

diff --git a/node_modules/apollo-link-http-common/lib/index.js b/node_modules/apollo-link-http-common/lib/index.js
index 05cd095..c79da1b 100644
--- a/node_modules/apollo-link-http-common/lib/index.js
+++ b/node_modules/apollo-link-http-common/lib/index.js
@@ -2,6 +2,7 @@
 Object.defineProperty(exports, "__esModule", { value: true });
 var tslib_1 = require("tslib");
 var printer_1 = require("graphql/language/printer");
+var utilities_1 = require("graphql/utilities/stripIgnoredCharacters");
 var ts_invariant_1 = require("ts-invariant");
 var defaultHttpOptions = {
     includeQuery: true,
@@ -91,6 +92,7 @@ exports.selectHttpOptionsAndBody = function (operation, fallbackConfig) {
         body.extensions = extensions;
     if (http.includeQuery)
         body.query = printer_1.print(query);
+        body.query = utilities_1.stripIgnoredCharacters(body.query);
     return {
         options: options,
         body: body,