ããã«ã¡ã¯ã Web ããã³ãã¨ã³ãã¨ã³ã¸ãã¢ã® @progfay ã§ãã
ä»åã¯ããã¸ã§ã¯ãã§ééãã URL é·ã«ãã GraphQL Request ã®å¤±æ㨠Apollo Link ã«ãã解決æ¹æ³ãç´¹ä»ãã¾ãã
å¼æ°ã«é åãåãåã GraphQL field
ç§ã®æå±ããã¹ã¿ãã£ãµããªä¸å¦è¬åº§ã®éçºããã¸ã§ã¯ã (é称: tara) ã§ã¯éä¿¡ã« GraphQL ãæ¡ç¨ãã¦ãã¾ãã
ãã®ä¸ã§ã以ä¸ã®ãã㪠field ãå®è£ ãã¦ãã¾ãã
type Query { entities(ids: [ID!]!): [Entity!]! }
ããã«å¯¾ãã¦ã以ä¸ã®ãã㪠Query ãå©ãã¾ãã
query specificEntities($ids: [ID!]!) { entities(ids: $ids) { name } }
Request-URI Too Large
ããæ¥ããã® field ã使ã£ã GraphQL Request ã§ã¨ã©ã¼ãçºçããã¨ããéç¥ã Sentry ããå±ãã¾ããã ã¨ã©ã¼ã®è©³ç´°ã調æ»ãã¦ããã¨ã Nginx ãã "414 Request-URI Too Large" ãè¿ã£ã¦ãã¦ãããã¨ããããã¾ããã
RFC2616 ã«ã¯ä»¥ä¸ã®ããã«è¨è¼ããã¦ãã¾ãã
The HTTP protocol does not place any a priori limit on the length of a URI. Servers MUST be able to handle the URI of any resource they serve, and SHOULD be able to handle URIs of unbounded length if they provide GET-based forms that could generate such URIs. A server SHOULD return 414 (Request-URI Too Long) status if a URI is longer than the server can handle (see section 10.4.15).
è¦ç´ããã¨ã URL ãé·ãã㦠Server ãå¦çã§ããªãå ´åã«è¿ã£ã¦ãã Status Code ã®ããã§ãã
ãã®ã¨ã©ã¼ã¯ Web Application ä¸ã«éããã Android 㨠iOS ã® Native Application ä¸ã§ãçºçãã¦ãã¾ããã
tara ã§ã¯ã»ãã¥ãªãã£ãããã©ã¼ãã³ã¹è¦³ç¹ã§ Persisted Query ãæ¡ç¨ãã¦ãã¾ãã
ãã㦠useGETForHashedQueries
option ãæå¹åãããã¨ã§ GraphQL Query ã® HTTP Request ã«ã¯ GET Request ãéãããããã«ãªã£ã¦ãã¾ãã
ä»å 414 ãçºçãããªã¯ã¨ã¹ãã¯ä»¥ä¸ã®ãã㪠URL ã«ãªã£ã¦ãã¾ããã
https://api.example.com/graphql?operationName=specificEntities&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%2206e6960d1a96c99e7d995d38ef380c91b7e09ea80d454267282225cc560275c7%22%7D%7D&variables=%7B%22ids%22%3A%5B%220%22%2C%221%22%2C%222%22%2C%223%22%2C%224%22%2C%225%22%2C%226%22%2C%227%22%2C...
è¦ã¥ããã®ã§ Query Parameter ãæ´çããã¨ã以ä¸ã®ããã«ãªã£ã¦ãã¾ãã
operationName
:specificEntities
extensions
:{"persistedQuery":{"version":1,"sha256Hash":"06e6960d1a96c99e7d995d38ef380c91b7e09ea80d454267282225cc560275c7"}}
variables
:{"ids":["0","1","2","3","4","5","6","7",...]}
URL ãé·ããªã£ã¦ãã¾ã£ã¦ããåå 㯠variables
ã® ids
ã«å¤§éã® ID ãå«ã¾ãã¦ããããã§ããã
Nginx ã§ããã° large_client_header_buffers
ãè¨å®ãããã¨ã§å¦çå¯è½ãª URL é·ã伸ã°ãã¾ãããæ ¹æ¬çãªè§£æ±ºã«ã¯ãªããªãããã§ãã
Client å´ããã®ã¢ããã¼ããæ¤è¨ãã
HTTP ãã£ãã·ã¥ãæ´»ããã¤ã¤ variables
ãé·ã GraphQL Request 㧠414 ãå¼ãèµ·ãããªãããã«ã¯ããURL ãé·ããªããããªå ´åã«ã¯ POST Request ã使ãããã§ããã°ããããã§ãã
ãããå®ç¾ããããã«ã¯ä»¥ä¸ã® 3 ã¤ãå¿ è¦ã§ãã
- Middleware çãªåå¨ã GraphQL Request ãéãç´åã«åã¾ãã
- æ¡ä»¶ã«å¿ã㦠GraphQL Request ã« POST Method ã使ããã
- Request ãéãåã« URL ãçµã¿ç«ã¦ã¦é·ãã確èªãã
Middleware çãªåå¨ã GraphQL Request ãéãç´åã«åã¾ãã
Apollo Client ã«ã¯ Apollo Link ã¨ãã Middleware ãããã¾ãã ãããå©ç¨ãããã¨ã§ Request ããç´åã Response ãè¿ã£ã¦ããç´å¾ã«æããã¨ãã§ãã¾ãã
Apollo Link ãä½æããæ¹æ³ã®ä¸ã¤ã¨ã㦠setContext
ãããã¾ãã
ããã¯å GraphQL ãæ㤠Context ã«ä»»æã®å¤ãã»ãããããã¨ãã§ãã Apollo Link ãä½æããé¢æ°ã§ãã
ä¾ãã°ä»¥ä¸ã®ãã㪠Apollo Link ãå©ç¨ãããã¨ã§ GraphQL Request ã«å¯¾ã㦠Header ã追å /ä¸æ¸ããããã¨ãã§ãã¾ãã
const setCustomHeaderLink = setContext((_, { headers }) => ({ headers: { ...headers, 'X-Client-Platform': 'Web', }, }))
æ¡ä»¶ã«å¿ã㦠GraphQL Request ã« POST Method ã使ããã
Apollo Client ã®ããã¥ã¡ã³ãæ°ãã context.fetchOption.method
ã« "POST"
ãã»ãããããã¨ã§ POST method ãå©ç¨ããããã¨ãã§ããããã§ãã
https://www.apollographql.com/docs/react/networking/advanced-http-networking/#overriding-options
const usePostMethodConditionallyLink = setContext((request) => ({ fetchOption: { ...fetchOption, method: shouldUsePostMethod(request) ? 'POST' : 'GET', }, }))
Request ãéãåã« URL ãçµã¿ç«ã¦ã¦é·ãã確èªãã
GraphQL Request ãéãåã« URL é·ãç®åºããæ¹æ³ã«ã¤ãã¦ã¯èª¿æ»ãã¦ãè¦ã¤ããã¾ããã§ããã ãããã Apollo Client ããã¯å®éã« GET Request ã® URL ãçæããã¦ãããããå¦çã®ã©ãã㧠URL ãçµã¿ç«ã¦ã¦ããã¯ãã§ãã ãã㧠Apollo Client ã®å®è£ ãè¦ã¦ã¿ããã¨ã¨ãã¾ããã
Apollo Client ããæä¾ããã¦ãã HTTP Client ã§ã¯å®éã« GraphQL Request ãè¡ãã¾ãã ããã®å®è£ ãè¦ãã° URL ã®çµã¿ç«ã¦ãè¡ãªã£ã¦ããã³ã¼ããè¦ã¤ããã¯ãã§ãã
å®éã® data fetch ã¯ããã§å®è¡ããã¦ãããã§ãã https://github.com/apollographql/apollo-client/blob/d470c964db46728d8a5dfc63990859c550fa1656/src/link/http/createHttpLink.ts#L121
fetch
ã«æ¸¡ããã¦ãã chosenURI
㯠rewriteURIForGET
ã§çæããã¦ãã¾ãã
https://github.com/apollographql/apollo-client/blob/8f79bb2547e549dadb9451c0541f174668a92bf1/src/link/http/createHttpLink.ts#L145
ãã®ã³ã¼ããåèã« URL ãçµã¿ç«ã¦ã¦é·ãã確èªãã Apollo Link ãæ¸ãã¦ã¿ã¾ãããã
import { Operation, Context, selectURI, selectHttpOptionsAndBody, fallbackHttpConfig, rewriteURIForGET } from '@apollo/client' export const calculateGetURI = (operation: Operation, context: Context) => { // https://github.com/apollographql/apollo-client/blob/8f79bb2547e549dadb9451c0541f174668a92bf1/src/link/http/createHttpLink.ts#L55 const uri = selectURI(operation, 'https://example.com/graphql') as string // https://github.com/apollographql/apollo-client/blob/8f79bb2547e549dadb9451c0541f174668a92bf1/src/link/http/createHttpLink.ts#L82-L87 const contextConfig = { http: context.http, options: context.fetchOptions, credentials: context.credentials, headers: context.headers, } // https://github.com/apollographql/apollo-client/blob/8f79bb2547e549dadb9451c0541f174668a92bf1/src/link/http/createHttpLink.ts#L90 // apollo-client ã®å®è£ å ã§ã¯ `selectHttpOptionsAndBodyInternal` ã使ããã¦ãã¾ããããã®é¢æ°ã¯ `@apollo/client` ãã export ããã¦ãã¾ããã // `selectHttpOptionsAndBody` 㯠`selectHttpOptionsAndBodyInternal` ãèã wrap ããã ãã®é¢æ°ã§ããããããå©ç¨ãã¦ãçµæã«å·®ã¯ã§ã¾ããã§ããã const { body } = selectHttpOptionsAndBody(operation, fallbackHttpConfig, {}, contextConfig) // https://github.com/apollographql/apollo-client/blob/8f79bb2547e549dadb9451c0541f174668a92bf1/src/link/http/createHttpLink.ts#L145 const { newURI } = rewriteURIForGET(uri, body) } const operation = { /* ... */ } as Operation const context = { /* ... */ } as Context const uri = calculateGetURI(operation, context) console.log(uri) // => https://example.com/graphql?operationName=testQuery&variables=...
ä¸è¨ã®ã³ã¼ãã§å©ç¨ãã selectURI
, selectHttpOptionsAndBody
, fallbackHttpConfig
, rewriteURIForGET
ã® 4 ã¤ã¯ undocumented ãªå®æ°ãé¢æ°ã§ããããã @apollo/client
ã®ã¢ãããã¼ãã«ãã£ã¦å¤æ´ããããä»åã®æå³ããå¦çãå®ç¾ã§ããªããªã£ããããå¯è½æ§ããããã¨ã«æ³¨æãã¦ãã ããã
@apollo/[email protected]
ã§ã¯ä¸è¨ã®ã³ã¼ãã§æå³ããéãã«åããã¨ã確èªãã¦ãããããããã§ã¯ä¸æ¦è§£æ±ºã¨ãã¾ãã
å®è£
ãGraphQL Request ã® URL ãé·ãããæã« POST Request ãå©ç¨ããããå®ç¾ããããã«å¿ è¦ãªæ¤è¨ãçµãããå®è£ ãå¯è½ããã ã¨ãããã¨ããããã¾ããã æçµçãªå®è£ ã¯ä»¥ä¸ã®ããã«ãªãã¾ããã
import { Operation } from '@apollo/client' import { setContext } from '@apollo/client/link/context' import { calculateGetURI } from './calculateGetURI' const POST_FALLBACK_URI_LENGTH_THRESHOLD = 1024 * 4 const isMutation = (operation: Operation) => operation.query.definitions.some((d) => d.kind === 'OperationDefinition' && d.operation === 'mutation') export const checkUriLengthLink = setContext((request, context) => { const operation: Operation = { ...request, variables: request.variables ?? {}, operationName: request.operationName ?? '', extensions: request.extensions ?? [], getContext: () => context, setContext: () => { throw new Error('not implemented `setContext` was called') }, } if (isMutation(operation) || context.fetchOptions.method === 'POST') { return {} } const uri = calculateGetURI(operation, context) if (uri === undefined) return {} const { size } = new Blob([uri], { type: 'text/plain' }) if (POST_FALLBACK_URI_LENGTH_THRESHOLD < size) { return { fetchOptions: { ...context.fetchOptions, method: 'POST', }, } } return {} })
POST_FALLBACK_URI_LENGTH_THRESHOLD
ã®å¤ã¯ Nginx ã« 414 ãè¿ãããªãããã«ããããã§ããã° 8KB 以ä¸ã§ããã°åé¡ããã¾ããã
ããã Google Chrome ã Safari ã§ã¯ 5KB 辺ããè¶
ãã㨠Connection Close ããã¦ãã¾ããããããã§ã¯ä½è£ãæã£ã¦ 4KB ãè¨å®ãã¦ãã¾ãã
ãããã«
æ¬è¨äºã§ã¯ã GraphQL Request ã® URL ãé·ãããæã« POST Request ãå©ç¨ãã checkUriLengthLink
ãç´¹ä»ãã¾ããã
Android ã§ã¯ apollo-kotlin
, iOS ã§ã¯ apollo-ios
ãå©ç¨ãã¦ããã @apollo/client
ã¨è¨è¨ææ³ãä¼¼ã¦ããã®ã§åºæ¬çã«ã¯åæ§ã®æé ã§ä¿®æ£ã§ãã¾ããã
åé¡ã®çºè¦ã Native App ã§ã®ä¿®æ£æ¹é ã対å¿ã¾ã§ @nkmrh ããããã£ã¦ãã ããã¾ããï¼ Special Thanks!!!
ã¹ã¿ãã£ãµããªã§ã¯ Application ã®æè¡ç課é¡ãä¸ç·ã«è§£æ±ºãã¦ãã仲éãåéãã¦ãã¾ãã