600 Matching Annotations
  1. Last 7 days
    1. Put that TS code in a file your app imports, for example, in remix.env.d.ts, and now the type of name will be the expected one.

      ts declare module "@remix-run/server-runtime" { export interface AppLoadContext { name: string; } }

    1. <table><tbody><tr><th colspan="4" rowspan="1">Status</th><th colspan="4" rowspan="1">Description</th></tr><tr><td colspan="5" rowspan="1">HIT</td><td colspan="5" rowspan="1">The resource was found in Cloudflare’s cache.</td></tr><tr><td colspan="5" rowspan="1">MISS</td><td colspan="5" rowspan="1">The resource was not found in Cloudflare’s cache and was served from the origin web server.</td></tr><tr><td colspan="5" rowspan="1">NONE/UNKNOWN</td><td colspan="5" rowspan="1">Cloudflare generated a response that denotes the asset is not eligible for caching. This may have happened because:
    2. A Worker generated a response without sending any subrequests. In this case, the response did not come from cache, so the cache status will be none/unknown.
    3. A Worker request made a subrequest (fetch). In this case, the subrequest will be logged with a cache status, while the main request will be logged with none/unknown status (the main request did not hit cache, since Workers sits in front of cache).
    4. A Firewall rule was triggered to block a request. The response will come from the edge network before it hits cache. Since there is no cache status, Cloudflare will log as none/unknown.
    5. A redirect page rule caused the edge network to respond with a redirect to another asset/URL. This redirect response happens before the request reaches cache, so the cache status is none/unknown.
    6. </td></tr><tr><td colspan="5" rowspan="1">EXPIRED</td><td colspan="5" rowspan="1">The resource was found in Cloudflare’s cache but was expired and served from the origin web server.</td></tr><tr><td colspan="5" rowspan="1">STALE</td><td colspan="5" rowspan="1">The resource was served from Cloudflare’s cache but was expired. Cloudflare could not contact the origin to retrieve an updated resource.</td></tr><tr><td colspan="5" rowspan="1">BYPASS</td><td colspan="5" rowspan="1">The origin server instructed Cloudflare to bypass cache via a Cache-Control header set to no-cache,private, or max-age=0 even though Cloudflare originally preferred to cache the asset. BYPASS is returned when enabling Origin Cache-Control. Cloudflare also sets BYPASS when your origin web server sends cookies in the response header.</td></tr><tr><td colspan="5" rowspan="1">REVALIDATED</td><td colspan="5" rowspan="1">The resource is served from Cloudflare’s cache but is stale. The resource was revalidated by either an If-Modified-Since header or an If-None-Match header.</td></tr><tr><td colspan="5" rowspan="1">UPDATING</td><td colspan="5" rowspan="1">The resource was served from Cloudflare’s cache and was expired, but the origin web server is updating the resource. UPDATING is typically only seen for very popular cached resources.</td></tr><tr><td colspan="5" rowspan="1">DYNAMIC</td><td colspan="5" rowspan="1">Cloudflare does not consider the asset eligible to cache and your Cloudflare settings do not explicitly instruct Cloudflare to cache the asset. Instead, the asset was requested from the origin web server. Use Page Rules to implement custom caching options.</td></tr></tbody></table>
    1. On the client, while we're waiting for our deferred promise to resolve, we can consume that stream to know how far along our process is.

      js const stream = useEventSource( `/items/${params.hash}/progress`, { event: "progress", }, )

    2. In Remix, we can use a resource route to make this endpoint, and our loader will return a stream that constant checks our JSON file for its progress.

      js export async function loader({ request, params, }: LoaderArgs) { const hash = params.hash return eventStream(request.signal, function setup(send) { const interval = setInterval(() => { const file = fs.readFileSync( path.join("public", "items", `${hash}.json`), ) if (file.toString()) { const data = JSON.parse(file.toString()) const progress = data.progress send({ event: "progress", data: String(progress) }) if (progress === 100) { clearInterval(interval) } } }, 200) return function clear(timer: number) { clearInterval(interval) clearInterval(timer) } }) }

    3. server sent events work by having an endpoint that does not immediately close its connection, and which sends a content type of text/event-stream.
  • Mar 2023
    1. Chunked encoding is useful when larger amounts of data are sent to the client and the total size of the response may not be known until the request has been fully processed. For example, when generating a large HTML table resulting from a database query or when transmitting large images.A chunked response looks like this:

      ```http HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked

      7\r\n Mozilla\r\n 11\r\n Developer Network\r\n 0\r\n \r\n ```

    2. chunked Data is sent in a series of chunks. The Content-Length header is omitted in this case and at the beginning of each chunk you need to add the length of the current chunk in hexadecimal format, followed by '\r\n' and then the chunk itself, followed by another '\r\n'. The terminating chunk is a regular chunk, with the exception that its length is zero. It is followed by the trailer, which consists of a (possibly empty) sequence of header fields.
    3. ```abnf Transfer-Encoding: chunked Transfer-Encoding: compress Transfer-Encoding: deflate Transfer-Encoding: gzip

      // Several values can be listed, separated by a comma Transfer-Encoding: gzip, chunked ```

    1. I beseech ye, my lord, let this venture be mine.

      When the Green Knight called the knights' loyalty into question, Gawain saw Arthur's humiliation and felt compelled to defend him. Sheri Ann Strite explains that "Gawain is free to choose his next act, and through his choice he reveals with which set of values he is aligned" (p. 5), Gawain saw this as a chance to prove his morality to Arthur. Gawain's bravery and loyalty to Arthur are demonstrated by his acceptance of the challenge when no one else would. Furthermore, his devotion to Arthur exemplifies the code of chivalry.

    1. ```js import { useState, useEffect } from 'react';

      interface StreamState { data: Uint8Array | null; error: Error | null; controller: AbortController; }

      const useAbortableStreamFetch = (url: string, options?: RequestInit): { data: Uint8Array | null, error: Error | null, abort: () => void, } => {

      const [state, setState] = useState<StreamState>({ data: null, error: null, controller: new AbortController(), });

      useEffect(() => { (async () => { try { const resp = await fetch(url, { ...options, signal: state.controller.signal, }); if (!resp.ok || !resp.body) { throw resp.statusText; }

          const reader = resp.body.getReader();
          while (true) {
            const { value, done } = await reader.read();
            if (done) {
              break;
            }
      
            setState(prevState => ({ ...prevState, ...{ data: value } }));
          }
        } catch (err) {
          if (err.name !== 'AbortError') {
            setState(prevState => ({ ...prevState, ...{ error: err } }));
          }
        }
      })();
      
      return () => state.controller.abort();
      

      }, [url, options]);

      return { data: state.data, error: state.error, abort: () => state.controller && state.controller.abort(), }; };

      export default useAbortableStreamFetch; ```

    1. Send the 304 Not Modified response

      ```js import etag from "etag"; import { renderToString } from "react-dom/server"; import type { EntryContext, HandleDataRequestFunction } from "remix"; import { RemixServer } from "remix";

      export default function handleRequest( request: Request, status: number, headers: Headers, remixContext: EntryContext ) { let markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );

      headers.set("Content-Type", "text/html"); headers.set("ETag", etag(markup));

      // check if the If-None-Match header matches the ETag if (request.headers.get("If-None-Match") === headers.get("ETag")) { // and send an empty Response with status 304 and the headers. return new Response("", { status: 304, headers }); }

      return new Response("<!DOCTYPE html>" + markup, { status, headers }); }

      export let handleDataRequest: HandleDataRequestFunction = async ( response: Response, { request } ) => { let body = await response.text();

      if (request.method.toLowerCase() === "get") { response.headers.set("etag", etag(body)); // As with document requests, check the If-None-Match header // and compare it with the Etag, if they match, send the empty 304 Response if (request.headers.get("If-None-Match") === response.headers.get("ETag")) { return new Response("", { status: 304, headers: response.headers }); } }

      return response; }; ```

    2. All Together

      ```js import etag from "etag"; import { renderToString } from "react-dom/server"; import type { EntryContext, HandleDataRequestFunction } from "remix"; import { RemixServer } from "remix";

      export default function handleRequest( request: Request, status: number, headers: Headers, remixContext: EntryContext ) { let markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );

      headers.set("Content-Type", "text/html"); headers.set("ETag", etag(markup));

      return new Response("<!DOCTYPE html>" + markup, { status, headers }); }

      export let handleDataRequest: HandleDataRequestFunction = async ( response: Response ) => { let body = await response.text(); response.headers.set("etag", etag(body)); return response; }; ```

    3. Using ETags for document requests

      ```js import etag from "etag"; import { renderToString } from "react-dom/server"; import type { EntryContext } from "remix"; import { RemixServer } from "remix";

      export default function handleRequest( request: Request, status: number, headers: Headers, remixContext: EntryContext ) { let markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );

      headers.set("Content-Type", "text/html"); // add the Etag header using the markup as value headers.set("ETag", etag(markup));

      return new Response("<!DOCTYPE html>" + markup, { status, headers }); } ```

    4. Using ETags for data requests

      ```js import etag from "etag"; import type { HandleDataRequestFunction } from "remix";

      export let handleDataRequest: HandleDataRequestFunction = async ( response: Response, { request } ) => { let body = await response.text(); // parse the response body as text

      // only add the ETag for GET requests if (request.method.toLowerCase() === "get") { response.headers.set("etag", etag(body)); // and use it to create the ETag }

      return response; // return the response }; ```

    1. ```js import { renderToString } from "react-dom/server"; import { RemixServer } from "remix"; import type { EntryContext } from "remix"; import { etag } from 'remix-etag';

      export default function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, remixContext: EntryContext ) { const markup = renderToString( <RemixServer context={remixContext} url={request.url} /> );

      responseHeaders.set("Content-Type", "text/html");

      const response = new Response("<!DOCTYPE html>" + markup, { status: responseStatusCode, headers: responseHeaders, }); return etag({ request, response }); } ```

  • developer.mozilla.org developer.mozilla.org
    1. ```http HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked Trailer: Expires

      7\r\n Mozilla\r\n 9\r\n Developer\r\n 7\r\n Network\r\n 0\r\n Expires: Wed, 21 Oct 2015 07:28:00 GMT\r\n \r\n ```

    1. Async iteration of a stream using for await...ofThis example shows how you can process the fetch() response using a for await...of loop to iterate through the arriving chunks.

      ```js const response = await fetch("https://www.example.org"); let total = 0;

      // Iterate response.body (a ReadableStream) asynchronously for await (const chunk of response.body) { // Do something with each chunk // Here we just accumulate the size of the response. total += chunk.length; }

      // Do something with the total console.log(total); ```

    1. The body read-only property of the Response interface is a ReadableStream of the body contents.
    1. You'll notice that for the app/routes/jokes/$jokeId.tsx route in addition to Cache-Control we've also set Vary header to Cookie. This is because we're returning something that's specific to the user who is logged in. So we want the cache to associated to that particular Cookie value and not shared with different users, so the browser and CDN will not deliver the cached value if the cookie is different from the cached response's cookie.
  • Feb 2023
    1. ```js const supportsRequestStreams = (() => { let duplexAccessed = false;

      const hasContentType = new Request('', { body: new ReadableStream(), method: 'POST', get duplex() { duplexAccessed = true; return 'half'; }, }).headers.has('Content-Type');

      return duplexAccessed && !hasContentType; })();

      if (supportsRequestStreams) { // … } else { // … } ```

    1. ```js /* * Fetch and process the stream / async function process() { // Retrieve NDJSON from the server const response = await fetch('http://localhost:3000/request');

      const results = response.body // From bytes to text: .pipeThrough(new TextDecoderStream()) // Buffer until newlines: .pipeThrough(splitStream('\n')) // Parse chunks as JSON: .pipeThrough(parseJSON());

      // Loop through the results and write to the DOM writeToDOM(results.getReader()); }

      /* * Read through the results and write to the DOM * @param {object} reader / function writeToDOM(reader) { reader.read().then(({ value, done }) => { if (done) { console.log("The stream was already closed!");

      } else {
        // Build up the values
        let result = document.createElement('div');
        result.innerHTML =
          `<div>ID: ${value.id} - Phone: ${value.phone} - Result: $
              {value.result}</div><br>`;
      
        // Prepend to the target
        targetDiv.insertBefore(result, targetDiv.firstChild);
      
        // Recursively call
        writeToDOM(reader);
      }
      

      }, e => console.error("The stream became errored and cannot be read from!", e) ); } ```

  • Jan 2023
    1. console $ curl -LH "Accept: application/vnd.schemaorg.ld+json" https://doi.org/10.5438/4K3M-NYVG { "@context": "http://schema.org", "@type": "ScholarlyArticle", "@id": "https://doi.org/10.5438/4k3m-nyvg", "url": "https://blog.datacite.org/eating-your-own-dog-food/", "additionalType": "BlogPosting", "name": "Eating your own Dog Food", "author": { "name": "Martin Fenner", "givenName": "Martin", "familyName": "Fenner", "@id": "https://orcid.org/0000-0003-1419-2405" }, "description": "Eating your own dog food is a slang term to describe that an organization should itself use the products and services it provides. For DataCite this means that we should use DOIs with appropriate metadata and strategies for long-term preservation for...", "license": "https://creativecommons.org/licenses/by/4.0/legalcode", "version": "1.0", "keywords": "datacite, doi, metadata, FOS: Computer and information sciences", "inLanguage": "en", "dateCreated": "2016-12-20", "datePublished": "2016-12-20", "dateModified": "2016-12-20", "isPartOf": { "@id": "https://doi.org/10.5438/0000-00ss", "@type": "CreativeWork" }, "citation": [ { "@id": "https://doi.org/10.5438/0012", "@type": "CreativeWork" }, { "@id": "https://doi.org/10.5438/55e5-t5c0", "@type": "CreativeWork" } ], "schemaVersion": "http://datacite.org/schema/kernel-4", "periodical": { "@type": "Series", "identifier": "10.5438/0000-00SS", "identifierType": "DOI" }, "publisher": { "@type": "Organization", "name": "DataCite" }, "provider": { "@type": "Organization", "name": "datacite" } }

    1. HTTP frames

      I'm guessing each frame has an identifier for the previous and next one. The server/user agent then parses all these frames for all the previous/next frames and gives the okay if they're all there or rerequests if not available. Something with TCP likely goes here.

  • Dec 2022
    1. GET if to obtain information. There should not be any "body" data sent with that request. This will not trigger error, but we just simply don't look there in GET request. Only query string parameters ale allowed. This request will never change anything, you can call it as many times as you want. It is called that GET is IDEMPOTENT request. POST is to add new or modify existing information. If request url doesn't contain resource ID then we will treat that as new data that must be inserted. If you want to modify existing data you need to specify an ID in the Rrequest URL (like POST /contacts/hg5fF). POST is NOT IDEMPOTENT, you need to be carefull to not send the same request twice... DELETE is only to remove infomration. As a general rule we do not allow to add any "body" data to that type of request nor we allow any query string. This should be just simply DELETE /contacts/hg5fF.