<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Steve Coffman</title>
    <description>The latest articles on DEV Community by Steve Coffman (@stevenacoffman).</description>
    <link>https://dev.to/stevenacoffman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F118419%2F4d35f44e-2249-4d52-8ee3-e634334eed36.png</url>
      <title>DEV Community: Steve Coffman</title>
      <link>https://dev.to/stevenacoffman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stevenacoffman"/>
    <language>en</language>
    <item>
      <title>#go</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Wed, 07 Jan 2026 00:25:59 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/go-1lh0</link>
      <guid>https://dev.to/stevenacoffman/go-1lh0</guid>
      <description></description>
    </item>
    <item>
      <title>Authorization (authz) and GraphQL</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Wed, 18 Dec 2024 19:50:20 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/authorization-authz-and-graphql-3ag9</link>
      <guid>https://dev.to/stevenacoffman/authorization-authz-and-graphql-3ag9</guid>
      <description>&lt;p&gt;Hi! I maintain &lt;a href="https://github.com/99designs/gqlgen" rel="noopener noreferrer"&gt;gqlgen&lt;/a&gt; a popular GraphQL library for Go. I've noticed many people stumbling on GraphQL authentication regardless of programming language. This is partly because they don't know what the possibilities are, what the tradeoffs of those possibilities are, or even what to ask for advice about.&lt;/p&gt;

&lt;p&gt;Hopefully, this article will clarify choices, and help you make informed decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Securing dynamic data access is hard
&lt;/h3&gt;

&lt;p&gt;GraphQL APIs are appealing because they are flexible, but this makes adding authorization difficult. In the REST world, you can (at a minimum) authorize individual endpoints, but in GraphQL, you have to find a way to authorize each query and mutation generically.&lt;/p&gt;

&lt;p&gt;In addition to the client/server behavior changes, GraphQL also introduces features like &lt;a href="https://www.apollographql.com/docs/federation/" rel="noopener noreferrer"&gt;federation&lt;/a&gt; that make it possible to deploy many different GraphQL services and expose them via a single unified API to clients. The big issue you face when building authorization in a distributed architecture is not having local access to all of the data required to make authorization decisions.&lt;/p&gt;

&lt;p&gt;The correct solution is (as always) dependent on your expected scale and complexity budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authorization models in GraphQL
&lt;/h3&gt;

&lt;p&gt;Imagine a user/subject with an ID like &lt;code&gt;id_1234567890&lt;/code&gt; that makes an HTTP POST request with a GraphQL mutation to rename a District with ID of &lt;code&gt;"47b3f42a-65c2-46b1-93d5-47ff4e92cf5b"&lt;/code&gt;. That looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;updateDistrict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"47b3f42a-65c2-46b1-93d5-47ff4e92cf5b"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How could you handle authorization (allow or deny) for this request in different authorization (authz) models?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Role-Based Access Control (RBAC)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Only the &lt;strong&gt;subject&lt;/strong&gt;’s &lt;strong&gt;role&lt;/strong&gt; determines whether to allow or deny access.
e.g. DevAdmin Role (“Can Edit District”)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Attribute-Based Access Control (ABAC)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The user/&lt;strong&gt;subject&lt;/strong&gt;, &lt;strong&gt;action&lt;/strong&gt;, and &lt;strong&gt;resource&lt;/strong&gt; are combined to determine whether to allow or deny access.
e.g. userID + &lt;code&gt;updateDistrict&lt;/code&gt; + district:ID would be allowed for users who have the Attribute of &lt;code&gt;owner&lt;/code&gt; for the district:ID of &lt;code&gt;47b3f42a-65c2-46b1-93d5-47ff4e92cf5b&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Policy-Based Access Control (PBAC)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Like ABAC but the rules are kept in policy documents. Attributes in rules can be on subject, object, or action.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == r.obj.Owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Relationship-Based Access Control (ReBAC)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Relationship-based access control (ReBAC) is both a subset of ABAC and a superset of RBAC. ReBAC builds a relationship graph between subjects and objects via relations. These relations include data ownership, parent-child relationships, groups, and hierarchies (or relation chains). Google’s Zanzibar proved this could be extremely low latency at a huge scale. Zanzibar’s relations are built out of tuples defined like:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tuple := (object, relation, user)
object := namespace:id
user :=  object | (object, relation)
relation := string
namespace := string
id := string
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;For instance, "User &lt;code&gt;id_1234567890&lt;/code&gt; &lt;em&gt;owns&lt;/em&gt; District &lt;code&gt;47b3f42a-65c2-46b1-93d5-47ff4e92cf5b&lt;/code&gt;" is represented as a tuple of &lt;code&gt;&amp;lt;resource&amp;gt;#&amp;lt;relation&amp;gt;@&amp;lt;subject&amp;gt;&lt;/code&gt;like:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;district:47b3f42a-65c2-46b1-93d5-47ff4e92cf5b#owner@user:id_1234567890
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The policy that operates on the graph of such tuples would always have both a &lt;code&gt;check_permission&lt;/code&gt; and &lt;code&gt;check_relation&lt;/code&gt; similar to:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allowed {
  ds.check_permission({
    "subject": {"id": input.user.id},
    "permission": "can-edit",
    "object": {"key": input.resource.district_id, "type": "district"},
  })
}

allowed {
  district = ds.object({"key": input.resource.district_id, "type": "district"})

  ds.check_relation({
    "subject": {"id": input.user.id},
    "relation": {"name": "manager_of", "type": "user"},
    "object": {"id": district.properties.owner_id},
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Graph-Based Access Control (GBAC)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Similar to how ReBAC builds a restricted graph, GBAC uses arbitrary queries of an unrestricted graph. For instance, in DGraph you can add schema directives with &lt;code&gt;@auth&lt;/code&gt; rules that are arbitrary GraphQL queries:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;updateDistrict&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"""
        query($USER: String!) {
            queryDistrict {
                owner(filter: { username: { eq: $USER } }) {
                    __typename
                }
            }
        }"""&lt;/span&gt;&lt;span class="err"&gt;})(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"47b3f42a-65c2-46b1-93d5-47ff4e92cf5b"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ad hoc ACLs or Centralized System?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Access Control List (ACL)&lt;/strong&gt;: An access control list (ACL) is a list of rules that specify which users or systems are granted or denied access to a particular object or system resource.&lt;/p&gt;

&lt;p&gt;For our example mutation, would we want the access control list of rules for it to be unique (bespoke or ad hoc), or compose it from some standard rules? These are some common ones that might be standardized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;OpenAccess&lt;/code&gt; is an ACL that is a no-op: it marks that this resolver is open-access. (Add a comment if it’s not obvious why!)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;IsLoggedIn&lt;/code&gt; checks if the request is made by some sort of authenticated user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;IsCurrentUser&lt;/code&gt; checks that the given user (target) and the current user (actor) are the same.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ActorHasPermission&lt;/code&gt; checks that the actor has a specified permission (TODO link).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;IsManagedByActor&lt;/code&gt; checks that the current user (actor) manages the resource (target).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ValidatesSecret&lt;/code&gt; checks that the request includes the correct shared secret.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Roles vs. Scopes
&lt;/h2&gt;

&lt;p&gt;Scopes are usually associated with API access. An API defines what scopes are available (what services it provides). For example, a user account management API might define scopes like &lt;code&gt;read:user&lt;/code&gt;, &lt;code&gt;create:user&lt;/code&gt;, &lt;code&gt;update:user&lt;/code&gt;. These are the capabilities the API provides, but not necessarily what any given user can do. In the &lt;code&gt;Role &amp;amp; Scope&lt;/code&gt; model, Roles are defined, and users are given a Role. Individual Scopes are associated with a given Role, combining all these elements together. For example, you might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: Audit, API: user_manager, Scopes: read:user
Role: Access Control, API:  user_manager, Scopes: create:user, update:user, read:user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if a user, Alice has the Audit role, while Bob has the Access Control role, then a GraphQL query of users might have the scope &lt;code&gt;read:user&lt;/code&gt; and allow them both, but a mutation to edit a user might have the scope &lt;code&gt;update:user&lt;/code&gt; and only Bob should be allowed to make that mutation request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to perform authorization in a GraphQL architecture?
&lt;/h2&gt;

&lt;p&gt;There’s a great article on &lt;a href="https://www.osohq.com/post/graphql-authorization" rel="noopener noreferrer"&gt;https://www.osohq.com/post/graphql-authorization&lt;/a&gt; that you should just read. Assuming that authentication (authn) has already happened and the user has some sort of authenticated identity token (by the way, &lt;a href="https://fly.io/blog/api-tokens-a-tedious-survey/#biscuits" rel="noopener noreferrer"&gt;this is an excellent article on the different token types&lt;/a&gt;), where should you perform authorization (authz)?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GraphQL resolver&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Often people put code into the beginning of their resolver like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCurrentUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ActorHasPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CanChangeUserData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsManagedByActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// return an unauthorized error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Directives&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
You could add directives to your schema like in &lt;a href="https://docs.wundergraph.com/docs/directives-reference/rbac-directive" rel="noopener noreferrer"&gt;wundergraph&lt;/a&gt; or &lt;a href="https://dgraph.io/blog/post/graphql-authorization/" rel="noopener noreferrer"&gt;DGraph&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@rbac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requireMatchAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;superadmin&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;updateDistrict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"47b3f42a-65c2-46b1-93d5-47ff4e92cf5b"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Middleware&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Middleware can decouple your authorization logic from your schema as with &lt;a href="https://github.com/maticzav/graphql-shield" rel="noopener noreferrer"&gt;GraphQL Shield&lt;/a&gt;. Middleware authorization works best for rules that apply to your whole schema at once i.e. every query and resolver, since middleware doesn’t have more specific information for more complicated domain rules. Good middleware rules could be used to filter out invalid tokens, reject non-safelisted queries, or to use &lt;a href="https://github.com/slicknode/graphql-query-complexity" rel="noopener noreferrer"&gt;calculated query complexity&lt;/a&gt; scores and reject overly expensive queries (e.g. &lt;a href="https://github.com/Warashi/compgen" rel="noopener noreferrer"&gt;see compgen&lt;/a&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Access Layer&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Since GraphQL can return partial results, authorization for reading data can be pushed below resolvers. If you are using PostgreSQL you can even use row and column-based access to push it all the way out of your app into your database! However, authorizing this deep is awkward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  without request and user-specific variables&lt;/li&gt;
&lt;li&gt;  for write access control&lt;/li&gt;
&lt;li&gt;  if your data is in more than one place.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Federated Gateway&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
Like middleware, this is either a great or terrible place for authorization. If there is no way to bypass a federated gateway, and you have only a few simple rules without needing domain or application-specific information then it can greatly simplify things. However, for more complicated needs (e.g. ABAC) trying to do authorization at the gateway is an example of &lt;a href="https://www.thoughtworks.com/radar/platforms/overambitious-api-gateways" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://www.thoughtworks.com/radar/platforms/overambitious-api-gateways" rel="noopener noreferrer"&gt;https://www.thoughtworks.com/radar/platforms/overambitious-api-gateways&lt;/a&gt; that encourages designs that are difficult to test and deploy. You can still use it like a middleware to filter out obvious bad actors and filter out invalid tokens, reject non-safelisted queries, or to &lt;a href="https://github.com/slicknode/graphql-query-complexity" rel="noopener noreferrer"&gt;use calculated query complexity&lt;/a&gt; scores and reject overly expensive queries &lt;/p&gt;&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;External Authorization System&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Using Policy engines like &lt;a href="https://authzed.com/spicedb" rel="noopener noreferrer"&gt;SpiceDB&lt;/a&gt;, &lt;a href="https://openfga.dev/" rel="noopener noreferrer"&gt;OpenFGA&lt;/a&gt;, &lt;a href="https://www.ory.sh/keto/" rel="noopener noreferrer"&gt;ORY Keto&lt;/a&gt;, &lt;a href="https://www.openpolicyagent.org/" rel="noopener noreferrer"&gt;OpenPolicy Agent (OPA)&lt;/a&gt;, let you put your&lt;br&gt;&lt;br&gt;
ReBAC rules in an external system and references them from your queries. The main benefit you get from the centralized relationships model is it makes it possible to manage authorization centrally. This means that development teams can create new applications and add new relationships without needing to update any application code.&lt;/p&gt;

&lt;p&gt;However, the downside is that you are constraining your application to use a very specific data model and you need to design your application around that data store.&lt;br&gt;&lt;br&gt;
At a certain scale, the balance tips towards centralization.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Identity token claims&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Tokens are great since HTTP is stateless. If you have a few roles (or claims), you can put them in the token, and your authorization logic is done. A claim of &lt;code&gt;mutation:*&lt;/code&gt; and &lt;code&gt;query:*&lt;/code&gt; would give full access, just as a role of &lt;code&gt;admin&lt;/code&gt; would. However, cookies are headers, and you are limited in the total size of any one header, as well as the total size of all your headers together.  &lt;/p&gt;

&lt;p&gt;Microsoft specifically &lt;a href="https://learn.microsoft.com/en-us/iis/get-started/whats-new-in-iis-10/http2-on-iis#when-is-http2-not-supported" rel="noopener noreferrer"&gt;calls out Windows authentication (NTLM/Kerberos/Negotiate) as not supported on HTTP/2&lt;/a&gt; due to HPACK performance issues that those large headers cause.&lt;br&gt;&lt;br&gt;
HTTP/2 sets a default 4K limit on all headers together. Beyond that, plan to set your relationship status to "It's Complicated" as you will need to negotiate increasing it with all your intervening infrastructure (load balancers, proxies, CDNs).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  nginx: &lt;a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers" rel="noopener noreferrer"&gt;link&lt;/a&gt; --&amp;gt; 8KB for 1 header max&lt;/li&gt;
&lt;li&gt;  envoy (used in istio): &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto" rel="noopener noreferrer"&gt;link&lt;/a&gt; --&amp;gt; 60 KB for headers&lt;/li&gt;
&lt;li&gt;  node.js: &lt;a href="https://nodejs.org/api/http.html#httpmaxheadersize" rel="noopener noreferrer"&gt;link&lt;/a&gt; --&amp;gt; 16KB for headers&lt;/li&gt;
&lt;li&gt;  traefik: &lt;a href="https://github.com/traefik/traefik/issues/8846" rel="noopener noreferrer"&gt;link&lt;/a&gt; --&amp;gt; re-uses go/http: 1MB for headers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  More Reading
&lt;/h3&gt;

&lt;p&gt;I cannot stress enough that you should read Patrick O'Dougherty's &lt;a href="https://www.osohq.com/post/graphql-authorization" rel="noopener noreferrer"&gt;GraphQL Authorization Patterns&lt;/a&gt; and Thomas Ptacek's &lt;a href="https://fly.io/blog/api-tokens-a-tedious-survey/" rel="noopener noreferrer"&gt;API Tokens: A Tedious Survey&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion: Wait, what is your advice?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://gqlgen.com/reference/directives/" rel="noopener noreferrer"&gt;Start with RBAC, and use schema directives, codegen&lt;/a&gt; and identity tokens. Then you can grow out of it to more complicated setups.&lt;/p&gt;

&lt;p&gt;Move as much authorization into the schema, so everything is more transparent and visible. It helps you to set good boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parting Questions
&lt;/h3&gt;

&lt;p&gt;What are your favorite authorization schema directives? What is your favorite token claim, attribute, policy, or role?&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>authz</category>
      <category>authorization</category>
    </item>
    <item>
      <title>HTTP/2 streaming and you, HTTP/3 streaming and wheee!</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Sat, 23 Nov 2024 19:59:21 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/http2-streaming-and-you-http3-streaming-and-wheee-23hb</link>
      <guid>https://dev.to/stevenacoffman/http2-streaming-and-you-http3-streaming-and-wheee-23hb</guid>
      <description>&lt;p&gt;&lt;strong&gt;HTTP/3 is used by 30.1%&lt;/strong&gt; &lt;a href="https://w3techs.com/technologies/details/ce-http3" rel="noopener noreferrer"&gt;of all the websites&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP/2 is used by 34.7%&lt;/strong&gt; &lt;a href="https://w3techs.com/technologies/details/ce-http2" rel="noopener noreferrer"&gt;of all the websites&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Some &lt;a href="https://blog.cloudflare.com/http3-usage-one-year-on/" rel="noopener noreferrer"&gt;methodologies&lt;/a&gt; put &lt;a href="https://pulse.internetsociety.org/blog/why-http-3-is-eating-the-world" rel="noopener noreferrer"&gt;these higher&lt;/a&gt;, but we can safely say:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A large chunk of current Internet traffic already uses HTTP/3 today.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, this change has been a quiet revolution in our industry.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP/2 (and HTTP/3) changes the rules of the game for web development!
&lt;/h2&gt;

&lt;p&gt;This is a summary of a number of other articles and my own experience building a &lt;a href="https://github.com/StevenACoffman/connect-box" rel="noopener noreferrer"&gt;jackbox clone using HTTP/3 Server Streaming using ConnectRPC&lt;/a&gt; (gRPC).&lt;/p&gt;

&lt;p&gt;People have become long accustomed to HTTP/1.1 and the best practices associated that have grown up around it, but a lot of these are worth re-evaluating now that things have changed, and it’s important to understand why.&lt;/p&gt;

&lt;p&gt;In HTTP/1.1 downloading multiple smaller files is slower than downloading a single file of the same combined size, because of the lookups, handshakes, and “queuing process” that happens.&lt;/p&gt;

&lt;p&gt;In HTTP/1.1 each connection is a single unary request/response cycle.&lt;/p&gt;

&lt;p&gt;Many Best Practices under HTTP/1.1 are actually counterproductive in HTTP/2 or HTTP/3 usage. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In HTTP/1.1 it is a best practice to open several HTTP 1.x connections in parallel to speedup the page loading (up to 6 max per domain shard).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In HTTP/1.1 it is a best practice to concatenate JavaScript and CSS files, sprite images and inline resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In HTTP/1.1 it is a best practice to design your APIs to batch multiple queries to minimize round trips.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In contrast, HTTP/2 (and HTTP/3) it is almost as if both gravity is reversed and all friction ceases to exist, as these well worn practices &lt;em&gt;degrade&lt;/em&gt; performance instead of enhancing performance!&lt;/p&gt;




&lt;p&gt;HTTP/2 TLS 1.3 hugely reduces the number of round trips required for setting up an HTTPS connection. For clients that have ever visited a site before, the data that traverses this connection is cryptographically protected, and there's no need to share key exchange protocol messages before transmission.&lt;/p&gt;

&lt;p&gt;HTTP/2 also supports multiplexing, which allows the Browser to fire off multiple requests at once on the same connection and receive the requests back in any order.&lt;/p&gt;

&lt;p&gt;HTTP/2 supports streams, a bidirectional flow of frames between the client and the server. A single client request can have multiple responses (e.g. subscribe to updates).&lt;/p&gt;

&lt;p&gt;HTTP/2 is full duplex across streams, in that one stream may be sending while another is receiving. But HTTP/2 is still very much half duplex within a given stream.&lt;/p&gt;

&lt;p&gt;HTTP/2 supports 100 max number of concurrent requests that a browser can make to a server up from 6-8.&lt;/p&gt;

&lt;p&gt;HTTP/3 is pretty much HTTP/2 but over UDP (yeah, they also improved and simplified things like stream prioritization). This prevents TCP's Head-Of-Line blocking problem, when a queue of TCP packets is held up by the first packet in the queue..&lt;/p&gt;




&lt;h3&gt;
  
  
  Websockets and HTTP/2
&lt;/h3&gt;

&lt;p&gt;HTTP/2 obsoletes websockets for all use cases except for pushing &lt;strong&gt;binary data&lt;/strong&gt; from the server to a &lt;strong&gt;JS webclient&lt;/strong&gt;. HTTP/2 fully supports binary bidi (bidirectional) streaming (read on), but browser JS doesn't have an API for consuming binary data frames and AFAIK such an API is not planned. Once the client opens a stream by sending a request, both sides can send DATA frames across a persistent socket at any time - full bidi.&lt;/p&gt;

&lt;p&gt;For every other application of bidi streaming, HTTP/2 is as good or better than websockets, because (1) the spec does more work for you, and (2) in many cases it allows fewer TCP connections to be opened to an origin.&lt;/p&gt;

&lt;p&gt;That's not much different from websockets: the client has to initiate a websocket upgrade request before the server can send data across, too.&lt;/p&gt;

&lt;p&gt;The biggest difference is that, unlike websockets, HTTP/2 defines its own multiplexing semantics: how streams get identifiers and how frames carry the id of the stream they're on. HTTP/2 also defines flow control semantics for prioritizing streams. This is important in most real-world applications of bidi.&lt;/p&gt;

&lt;p&gt;If you need to build a real-time chat app, let's say, where you need to broadcast new chat messages to all the clients in the chat room that have open connections, you can (and probably should) do this without websockets.&lt;/p&gt;

&lt;p&gt;You would use Server-Sent Events to push messages down and the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;Fetch&lt;/a&gt; api to send requests up. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format" rel="noopener noreferrer"&gt;Server-Sent Events&lt;/a&gt; (SSE) is a little-known but &lt;a href="https://caniuse.com/#feat=eventsource" rel="noopener noreferrer"&gt;well supported&lt;/a&gt; API that exposes a message-oriented server-to-client stream. Although it doesn't look like it to the client JavaScript, under the hood your browser (if it supports HTTP/2) will reuse a single TCP connection to multiplex all of those messages. There is no efficiency loss and in fact it's a gain over websockets because all the other requests on your page are also sharing that same TCP connection. Need multiple streams? Open multiple EventSources! They'll be automatically multiplexed for you.&lt;/p&gt;

&lt;p&gt;Besides being more resource efficient and having less initial latency than a websocket handshake, Server-Sent Events have the nice property that they automatically fall back and work over HTTP/1.1 (but the 6 connection per domain limit). But when you have an HTTP/2 connection they work incredibly well.&lt;/p&gt;

&lt;p&gt;Here's a good article with a &lt;a href="https://building.lang.ai/our-journey-from-websockets-to-http-2-4d069c54effd" rel="noopener noreferrer"&gt;real-world example&lt;/a&gt; of accomplishing the reactively-updating SPA.&lt;/p&gt;

&lt;p&gt;It is worth mentioning that websockets over HTTP/2 and even websockets over HTTP/3 (UDP QUIC) are also possible, and do notably improve performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  HTTP/2 GraphQL impact is a mixed bag
&lt;/h2&gt;

&lt;p&gt;With HTTP/2 (and HTTP/3) &lt;a href="https://www.mnot.net/blog/2019/10/13/h2_api_multiplexing" rel="noopener noreferrer"&gt;&lt;strong&gt;batching&lt;/strong&gt; requests like this is now &lt;strong&gt;counter-productive&lt;/strong&gt; to performance&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="no"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;widgets&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5382894223&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;35223231&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;534232313&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5231332435&lt;/span&gt; &lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;
&lt;span class="nl"&gt;Accept:&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;widgets&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="nl"&gt;Host:&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;widgets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;org&lt;/span&gt;
&lt;span class="nc"&gt;Accept&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nl"&gt;Encoding:&lt;/span&gt; &lt;span class="n"&gt;gzip&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deflate&lt;/span&gt;
&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nl"&gt;Agent:&lt;/span&gt; &lt;span class="nc"&gt;BobsWidgetClient&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The above approach has a number of downsides. Both the service and clients need to understand a new endpoint, and a new list-based format, bloating the API – especially if there are many different types of resources that need similar treatment. Furthermore, this approach seriously impacts cache efficiency, creating further server load and client-perceived latency.&lt;/p&gt;

&lt;p&gt;If you can describe precisely what you want to the server in an efficient format, it can now reply with just what you want.&lt;/p&gt;

&lt;p&gt;Without too much exaggeration, another way to think of HTTP/2 is as a new query language – one that lets you encode a very complex set of requests into a small amount of data that is heavily optimized for transmission, while still allowing standard HTTP components – especially caches – to work with the individual requests. However, this is still &lt;strong&gt;server-centric&lt;/strong&gt; as it currently requires that the server backend provides endpoints that are &lt;strong&gt;exactly&lt;/strong&gt; as granular as the client needs, rather than allowing the client to independently decide granularity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By using HTTP/2, &lt;a href="https://github.com/dunglas/vulcain/blob/main/docs/graphql.md" rel="noopener noreferrer"&gt;it fixes &lt;em&gt;most&lt;/em&gt; problems caused by compound documents and sparse fieldsets based formats such as GraphQL and JSON:API&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Because each pushed resource is sent in a separate HTTP/2 stream (HTTP/2 multiplexing), related resources can be sent in parallel to the client.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consequently, clients and network intermediates (such as a CDN or proxy), can store each resource in a specific cache, while resource embedding only allows to have the full big JSON document in cache, &lt;a href="https://en.wikipedia.org/wiki/Cache_invalidation" rel="noopener noreferrer"&gt;cache invalidation&lt;/a&gt; is then more efficient, and can be done at the HTTP level.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Specifically with GraphQL, using cache mechanisms provided by the HTTP protocol isn't easy (&lt;code&gt;POST&lt;/code&gt; requests cannot be cached using just HTTP semantics).&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP/2 and GraphQL Federation
&lt;/h3&gt;

&lt;p&gt;Apollo Federation lets you declaratively combine multiple APIs into a single, federated graph. This federated graph enables clients to interact with your APIs through a &lt;strong&gt;single request&lt;/strong&gt;. That’s &lt;strong&gt;no longer desirable&lt;/strong&gt; from a performance standpoint in HTTP/2 or HTTP/3. In federation, the slowest subquery becomes the bottleneck for the entire federated query. Federation also increases the difficulty of independent deployability and testability. It also makes it hard to provide the client with meaningful partial success and partial errors when subqueries return errors. It may be that there are other specific concerns — such as authentication and rate limiting — where a GraphQL gateway is still more useful than not.&lt;/p&gt;

&lt;p&gt;Apollo Federation also does not support GraphQL Subscriptions, and they have no plans to add support&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP method QUERY and GraphQL
&lt;/h3&gt;

&lt;p&gt;The IETF WHATWG has a &lt;a href="https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-06.html" rel="noopener noreferrer"&gt;draft HTTP extension&lt;/a&gt; that would add a new HTTP method, QUERY, as a &lt;strong&gt;safe&lt;/strong&gt;, idempotent request method that can carry request content. (which would apply to HTTP/1.1, HTTP/2, and HTTP/3). This specification defines an HTTP method is &lt;strong&gt;safe&lt;/strong&gt; if it doesn't alter the state of the server. In other words, a method is safe if it leads to a read-only operation.&lt;/p&gt;

&lt;p&gt;This extension would allow complicated &lt;strong&gt;client-centric queries&lt;/strong&gt; like GraphQL to avoid using POST and be able to enjoy more standard HTTP caching behavior. When combined with the other performance benefits of HTTP/2 and HTTP/3 it should open new opportunities for dynamic client interactions.&lt;/p&gt;

&lt;p&gt;As to how active the proposal is, you can see a lot of revisions in the GitHub history of the source document here: &lt;a href="https://github.com/httpwg/http-extensions/commits/main/draft-ietf-httpbis-safe-method-w-body.xml" rel="noopener noreferrer"&gt;https://github.com/httpwg/http-extensions/commits/main/draft-ietf-httpbis-safe-method-w-body.xml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And use the IETF data tracker to see the other activity on mailing lists:&lt;br&gt;&lt;br&gt;
&lt;a href="https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/" rel="noopener noreferrer"&gt;https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Node 22.2.0 supports HTTP Method Query &lt;a href="https://github.com/nodejs/node/issues/51562" rel="noopener noreferrer"&gt;https://github.com/nodejs/node/issues/51562&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL Subscriptions over HTTP/2
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://wundergraph.com/blog/deprecate_graphql_subscriptions_over_websockets#summary-of-the-problems-with-graphql-subscriptions-over-websockets" rel="noopener noreferrer"&gt;Summary of the Problems with GraphQL Subscriptions over WebSockets&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;WebSockets make your GraphQL Subscriptions stateful&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSockets cause the browser to fall back to HTTP/1.1&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSockets cause security problems by exposing Auth Tokens to the client&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSockets allow for bidirectional communication, but GraphQL subscriptions do not&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSockets are not ideal for SSR (server-side-rendering)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GraphQL Subscriptions over WebSockets cause a few problems with performance, security and usability.&lt;/p&gt;

&lt;h3&gt;
  
  
  GraphQL Subscriptions over HTTP/2 with Server Sent Events (SSE)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;HTTP/2 SSE (Server-Sent Events) / Fetch is stateless&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP/2 SSE (Server-Sent Events) / Fetch can easily be secured&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP/2 SSE (Server-Sent Events) / Fetch disallows the client to send arbitrary data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP/2 SSE (Server-Sent Events) / Fetch can easily be used to implement SSR (Server-Side Rendering) for GraphQL Subscriptions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sources and further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mnot.net/blog/2019/10/13/h2_api_multiplexing" rel="noopener noreferrer"&gt;How Multiplexing Changes Your HTTP APIs
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://building.lang.ai/our-journey-from-websockets-to-http-2-4d069c54effd" rel="noopener noreferrer"&gt;Our Journey from Websockets to HTTP/2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apisyouwonthate.com/blog/lets-stop-building-apis-around-a-network-hack/" rel="noopener noreferrer"&gt;Let's Stop Building APIs Around a Network Hack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xuorig.medium.com/is-graphql-still-relevant-in-an-http2-world-64964f207b8" rel="noopener noreferrer"&gt;Is GraphQL Still Relevant in an HTTP2 World&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dunglas/vulcain/blob/main/docs/graphql.md#comparison-with-graphql-and-other-api-formats" rel="noopener noreferrer"&gt;Vulcain Comparison with GraphQL and other API Formats&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>http3</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Similar Event De-duplication per Period</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Fri, 06 Sep 2024 13:43:19 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/similar-event-de-duplication-per-period-1m0d</link>
      <guid>https://dev.to/stevenacoffman/similar-event-de-duplication-per-period-1m0d</guid>
      <description>&lt;p&gt;Event orientation is great! &lt;/p&gt;

&lt;p&gt;Whether you are using GCP Pub/Sub, Kafka, Kinesis, RabbitMQ, NATS JetStream, Redis Pub/Sub or any of the myriad alternatives, the Patterns you learn apply to them all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Similar Event de-duplication per period
&lt;/h3&gt;

&lt;p&gt;Even if you are enjoying exactly-once-delivery, you will still get similar events that you don't really want to react to multiple times.&lt;/p&gt;

&lt;p&gt;A great example of this is actionable alerts. The first time something notices a problem, that's great to escalate to get someone's attention that an action is required. The 700th time is just noise.&lt;/p&gt;

&lt;p&gt;If you are sending an event you have fields (whether it is JSON/protobuf/struct whatever), and you just need to identify which fields to group things by to sort them into the same bucket for your time period.&lt;/p&gt;

&lt;p&gt;You can take a hash of any arbitrary set of those event fields and calculate a hash of them be the key to some persistence source (key value storage, SQL, whatever) For instance, in Go: &lt;a href="https://go.dev/play/p/Ain8FIJiDit" rel="noopener noreferrer"&gt;https://go.dev/play/p/Ain8FIJiDit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then store that hash with an expiration timestamp and. If you encounter any more "similar" events before the expiration timestamp, ignore them, as they have already been brought to someone's attention. &lt;/p&gt;

&lt;h3&gt;
  
  
  Real Life Example
&lt;/h3&gt;

&lt;p&gt;At work, we deal with school districts, and they provide us with their roster, which synchronize with on a regular basis (nightly). But sometimes a human at the school district will make a mistake like accidentally deleting all the students in their roster. Whoops! However, if they haven't fixed the problem, we actually don't need to continue to be reminded more than once.&lt;/p&gt;

&lt;p&gt;The district, and the reason we were unable to roster it, are thecomposite unique keys.&lt;/p&gt;

&lt;p&gt;Whenever we would file a JIRA ticket (or send an alert to Slack), we first compute the hash of those two keys and see if a matching hash has already been sent, and if so if the last alert has expired (24 hrs or something) before sending a new one and replacing the old one. So for instance, a roster failure for a district could have something a generic as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"district"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"00be2b9c-ef18-4c27-8fa9-087dd5f39f27"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"attention"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"rosterops"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"reason"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"roster change exceeds threshold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and you wouldn’t hear about that district failing to roster again until the next day. But if someone manually tried to sync up and that district had a different type of failure that occurred (re-running now gives "service unavailable" instead of exceeding the change tolerance threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"district"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"00be2b9c-ef18-4c27-8fa9-087dd5f39f27"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"attention"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"rosterops"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"reason"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"roster provider is unavailable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, We can take a hash of any arbitrary set of those fields and make that an indexed datastore field on an alert entity.  &lt;a href="https://go.dev/play/p/Ain8FIJiDit" rel="noopener noreferrer"&gt;https://go.dev/play/p/Ain8FIJiDit&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Extra bonus
&lt;/h3&gt;

&lt;p&gt;If you also persist another field like "status" you can see if someone is already handling it and make it into a nice action item tracker for any arbitrary alert system.&lt;/p&gt;

</description>
      <category>go</category>
      <category>events</category>
      <category>pubsub</category>
    </item>
    <item>
      <title>Browser Client to gRPC Server Routing options: Connect, gRPC-web, gRPC-gateway and more!</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Fri, 09 Aug 2024 21:39:05 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/browser-client-to-grpc-server-routing-options-connect-grpc-web-grpc-gateway-and-more-52cm</link>
      <guid>https://dev.to/stevenacoffman/browser-client-to-grpc-server-routing-options-connect-grpc-web-grpc-gateway-and-more-52cm</guid>
      <description>&lt;h1&gt;
  
  
  Browser Client to gRPC Server Routing options: Connect, gRPC-web, gRPC-gateway and more!
&lt;/h1&gt;

&lt;p&gt;These days, gRPC is commonly used and well-supported enough that many options exist for communicating to a gRPC backend from the browser.&lt;/p&gt;

&lt;p&gt;For gRPC apps, I quite like using &lt;a href="https://connectrpc.com/" rel="noopener noreferrer"&gt;Connect&lt;/a&gt;. Connect is a family of libraries for building browser and gRPC-compatible HTTP APIs: you write a short Protocol Buffer schema and implement your application logic, and Connect generates code to handle marshaling, routing, compression, and content type negotiation. It also generates an idiomatic, type-safe client in any supported language.&lt;/p&gt;

&lt;p&gt;Connect lets you choose from gRPC, gRPC-Web, and Connect's own protocol, but each protocol has a different set of production routing options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing a protocol and how to route it!
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://connectrpc.com/docs/introduction#seamless-multi-protocol-support" rel="noopener noreferrer"&gt;Connect: Choosing a protocol&lt;/a&gt; shows connect supports gRPC-web as well, but I thought I'd expand on how routing works from the browser for these different protocol choices. As a refresher, this is from Connect's docs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Connect has Seamless multi-protocol support
&lt;/h4&gt;

&lt;p&gt;Connect servers and clients support three protocols: gRPC, gRPC-Web, and&lt;br&gt;
Connect's own protocol.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect fully supports the &lt;a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md" rel="noopener noreferrer"&gt;gRPC protocol&lt;/a&gt;, including
streaming, trailers, and error details. Any gRPC client, in any language, can
call a Connect server, and Connect clients can call any gRPC server. We
validate our gRPC compatibility with an &lt;a href="https://github.com/connectrpc/conformance" rel="noopener noreferrer"&gt;extended version&lt;/a&gt;
of Google's own interoperability tests.&lt;/li&gt;
&lt;li&gt;Connect also offers direct support for the &lt;a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md" rel="noopener noreferrer"&gt;gRPC-Web protocol&lt;/a&gt; used by &lt;a href="https://github.com/grpc/grpc-web" rel="noopener noreferrer"&gt;grpc/grpc-web&lt;/a&gt;, without relying
on a translating proxy like Envoy.&lt;/li&gt;
&lt;li&gt;Finally, Connect supports &lt;a href="https://connectrpc.com/docs/protocol" rel="noopener noreferrer"&gt;its own protocol&lt;/a&gt;: a
straightforward HTTP-based protocol that works over HTTP/1.1, HTTP/2, and
HTTP/3. It takes the best parts of gRPC and gRPC-Web, including streaming,
and packages them into a protocol that's equally at home in browsers,
monoliths, and microservices. By default, implementations support both JSON-
and binary-encoded Protobuf. You can call our live &lt;a href="https://github.com/connectrpc/examples-go" rel="noopener noreferrer"&gt;demo service&lt;/a&gt; with
cURL:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

  curl &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{"sentence": "I feel happy."}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, Connect servers support ingress from all three protocols. Clients&lt;br&gt;
default to using the Connect protocol, but can switch to gRPC or gRPC-Web with&lt;br&gt;
a configuration toggle — no further code changes required. The APIs for&lt;br&gt;
errors, headers, trailers, and streaming are all protocol-agnostic.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Connect protocol HTTP Host and Request Path Routing
&lt;/h3&gt;

&lt;p&gt;We get to use traditional HTTP Routing!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;a href="https://connectrpc.com/docs/protocol" rel="noopener noreferrer"&gt;Connect protocol&lt;/a&gt; is a simple, POST-only protocol that works over HTTP/1.1 or HTTP/2 (or HTTP/3!). It takes the best portions of gRPC and gRPC-Web, including streaming, and packages them into a protocol that works equally well in browsers, monoliths, and microservices. The Connect protocol is what we think the gRPC Protocol should be. By default, JSON- and binary-encoded Protobuf is supported. Calling a Connect API is as easy as using curl:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


# Try it out! This is a live demo!
curl \
    --header "Content-Type: application/json" \
    --data '{"sentence": "I feel happy."}' \
    https://demo.connectrpc.com/connectrpc.eliza.v1.ElizaService/Say


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflfnhncd6zh2k8lvrsc2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflfnhncd6zh2k8lvrsc2.png" alt="Connect RPC Web"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have a load balancer, you can use all the familiar HTTP/1.1 or HTTP/2 (or HTTP/3) routing rules to get to the backend (Host or Host+Path). This is my preferred option, and you can use your favorite load balancer. I like Envoy these days.&lt;/p&gt;

&lt;p&gt;And inside your server, HTTP/1.1 or HTTP/2 (or HTTP/3) for the Connect protocol would be based on the URL &lt;code&gt;/connectrpc.eliza.v1.ElizaService&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  gRPC Routing
&lt;/h3&gt;

&lt;p&gt;In Kubernetes, the common Gateway API spec has experimental support &lt;a href="https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute" rel="noopener noreferrer"&gt;GRPCRoute&lt;/a&gt;. AFAICT &lt;a href="https://gateway.envoyproxy.io/" rel="noopener noreferrer"&gt;Envoy Gateway&lt;/a&gt; has the most mature support for Gateway API GRPCRoute of the &lt;a href="https://gateway-api.sigs.k8s.io/implementations/" rel="noopener noreferrer"&gt;implementations&lt;/a&gt;. At least GKE &lt;a href="https://cloud.google.com/kubernetes-engine/docs/tutorials/exposing-grpc-services-on-gke-using-envoy-proxy" rel="noopener noreferrer"&gt;specifically recommends using Envoy as your gRPC gateway&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The source code for that tutorial is &lt;a href="https://github.com/GoogleCloudPlatform/grpc-gke-nlb-tutorial" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note: Browsers do not support gRPC protocol natively because they do not support HTTP/2 Trailers, nor does the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API" rel="noopener noreferrer"&gt;Browser's Fetch API&lt;/a&gt; support client streaming. The fetch API does specify streaming request bodies, but unfortunately, browser vendors have not come to an agreement to support streams from the client—&lt;a href="https://github.com/whatwg/fetch/issues/1438" rel="noopener noreferrer"&gt;see this WHATWG issue on GitHub&lt;/a&gt;. This means you can use streaming from the browser, but only server streaming.&lt;/p&gt;




&lt;h3&gt;
  
  
  gRPC-Gateway: JSON all the things
&lt;/h3&gt;

&lt;p&gt;The gRPC-Gateway project started out after the original author,&lt;a href="https://github.com/yugui" rel="noopener noreferrer"&gt;Yuki Yugui Sonoda&lt;/a&gt;, left Google and wanted to use a tool they had been using internally at Google, but couldn’t find an open-source version. So they wrote one!&lt;/p&gt;

&lt;p&gt;At its core the gRPC-Gateway is a translation layer between the HTTP/JSON REStful API paradigm and the gRPC/Protobuf API paradigm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jbrandhorst.com/post/grpc-in-the-browser/#the-grpc-gateway" rel="noopener noreferrer"&gt;Johan Brandhorst-Satzkorn wrote an excellent article on the ins and outs of gRPC-Gateway&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12dma7yrcyysxxkfrfg8.png" alt="gRPC-Gateway JSON API"&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vanguard: JSON all the things, but also Connect
&lt;/h3&gt;

&lt;p&gt;&lt;a href="//github.com/connectrpc/vanguard-go"&gt;Vanguard&lt;/a&gt; is a nearly seamless replacement for gRPC-Gateway. It replaces the JSON/REST to gRPC translation, but given that it also can translate JSON/REST to Connect, it opens up a few more options.&lt;/p&gt;




&lt;h3&gt;
  
  
  gRPC-web Routing options
&lt;/h3&gt;

&lt;p&gt;There are two different options, and both of them involve using a proxy like Envoy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabxv70sxn9syegngyikq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabxv70sxn9syegngyikq.png" alt="gRPC-web Envoy proxy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  grpc-web option 1: grpc-web
&lt;/h5&gt;

&lt;p&gt;In grpc-web, traditional HTTP URL routing works. For instance:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

curl 'http://localhost:8080/grpc.gateway.testing.EchoService/Echo' 
-H 'Pragma: no-cache' 
-H 'X-User-Agent: grpc-web-javascript/0.1' 
-H 'Origin: http://localhost:8081' 
-H 'Content-Type: application/grpc-web+proto' 
-H 'Accept: */*' 
-H 'X-Grpc-Web: 1' 
-H 'Connection: keep-alive' --data-binary $'\x00\x00\x00\x00\x05\n\x03abc' --compressed


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The URL &lt;code&gt;/grpc.gateway.testing.EchoService&lt;/code&gt; would be routed to the service.&lt;/p&gt;

&lt;h5&gt;
  
  
  grpc-web option 2: gRPC bridging
&lt;/h5&gt;

&lt;p&gt;Envoy also supports &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/other_protocols/grpc#grpc-bridging" rel="noopener noreferrer"&gt;gRPC bridging&lt;/a&gt; where gRPC-web is translated to gRPC.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://github.com/connectrpc/envoy-demo/issues/2" rel="noopener noreferrer"&gt;https://github.com/connectrpc/envoy-demo/issues/2&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Envoy notes
&lt;/h3&gt;

&lt;p&gt;From &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http3" rel="noopener noreferrer"&gt;Envoy HTTP/3 overview&lt;/a&gt;: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While HTTP/3 &lt;strong&gt;downstream&lt;/strong&gt; support is deemed ready for production use, improvements are ongoing, tracked in the area-quic tag.&lt;/p&gt;

&lt;p&gt;HTTP/3 &lt;strong&gt;upstream&lt;/strong&gt; support is alpha - key features are implemented but have not been tested at scale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/envoyproxy/gateway/issues/422" rel="noopener noreferrer"&gt;https://github.com/envoyproxy/gateway/issues/422&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  From Gophers Slack regarding gRPC routing in GKE:
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;No particular opinions, it's fine. Google gave up on actually fixing their ingress controller and came up with the Gateway spec instead, It's not super widely supported in the rest of the k8s ecosystem yet. That said, it works very well on GKE for a lot of things.&lt;/p&gt;

&lt;p&gt;For anything http1.1 it should work out of the box as an L7 LB.&lt;/p&gt;

&lt;p&gt;For http2 to work properly it's likely that between the LB and app it needs to be configured to do TLS, because GCP LBs (regardless if configured with ingress or gateway) does not support &lt;code&gt;h2c&lt;/code&gt; (http2 over cleartext) afaik. GKE only implements the &lt;code&gt;HTTPRoute&lt;/code&gt; part of the Gateway spec also, so can't do anything protocol-aware for grpc or have tcp vs udp routes either.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/intro/terminology" rel="noopener noreferrer"&gt;Envoy Terminology&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Downstream: A downstream host connects to Envoy, sends requests, and receives responses.&lt;/li&gt;
&lt;li&gt;Upstream: An upstream host receives connections and requests from Envoy and returns responses.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration" rel="noopener noreferrer"&gt;xDS configuration API overview&lt;/a&gt; - gRPC/REST based configuration provider APIs known as “xDS” (* discovery service)

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#fully-static" rel="noopener noreferrer"&gt;fully-static&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#eds" rel="noopener noreferrer"&gt;EDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#cds" rel="noopener noreferrer"&gt;CDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#rds" rel="noopener noreferrer"&gt;RDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#vhds" rel="noopener noreferrer"&gt;VHDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#srds" rel="noopener noreferrer"&gt;SRDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#lds" rel="noopener noreferrer"&gt;LDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#sds" rel="noopener noreferrer"&gt;SDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#rtds" rel="noopener noreferrer"&gt;RTDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#ecds" rel="noopener noreferrer"&gt;ECDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#aggregated-xds-ads" rel="noopener noreferrer"&gt;Aggregated xDS (“ADS”)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#delta-grpc-xds" rel="noopener noreferrer"&gt;Delta gRPC xDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration#xds-ttl" rel="noopener noreferrer"&gt;xDS TTL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>networking</category>
      <category>grpc</category>
      <category>envoy</category>
      <category>browser</category>
    </item>
    <item>
      <title>GitHub Actions and checking Tokens for expiration</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Sun, 14 Jul 2024 15:08:50 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/github-actions-and-checking-tokens-for-expiration-p2g</link>
      <guid>https://dev.to/stevenacoffman/github-actions-and-checking-tokens-for-expiration-p2g</guid>
      <description>&lt;h3&gt;
  
  
  What is a GitHub token?
&lt;/h3&gt;

&lt;p&gt;GitHub lets you &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens" rel="noopener noreferrer"&gt;create a Personal Access Token (PAT) for your GitHub user&lt;/a&gt;. These types of tokens are used pretty much the same as &lt;a href="https://docs.github.com/en/actions/security-guides/automatic-token-authentication" rel="noopener noreferrer"&gt;the GitHub Action default token&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;A GitHub Action default &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; expires when a job finishes or after a maximum of 24 hours.&lt;/p&gt;

&lt;p&gt;Your GitHub Personal Access Token (PAT) may expire when it's past its expiration date, but GitHub may also &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/token-expiration-and-revocation" rel="noopener noreferrer"&gt;expire or revoke it for a variety of other reasons&lt;/a&gt;. It is pretty tedious to tell which if any of those other reasons have happened.&lt;/p&gt;

&lt;p&gt;In either case, both the official GitHub Actions (like &lt;a href="https://github.com/actions/checkout/issues/664" rel="noopener noreferrer"&gt;checkout&lt;/a&gt;) and the marketplace ones will often fail with a very unhelpful error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: fatal: could not read Username for 'https://github.com': terminal prompts disabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The layers of their JavaScript between you and a &lt;code&gt;git&lt;/code&gt; command will never be updated to make more friendly error messages upon failure.&lt;/p&gt;

&lt;p&gt;Instead, if you add a step to your own workflow you can emit helpful action items for when these errors occur and save your future self from wasting time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify GitHub Token has access (and is not expired)
&lt;/h2&gt;

&lt;p&gt;You can &lt;a href="https://support.snyk.io/hc/en-us/articles/4410967220765-How-to-check-if-a-personal-access-token-has-the-correct-permissions" rel="noopener noreferrer"&gt;verify your Personal Access Token is not expired and has proper permissions&lt;/a&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -XGET -H 'authorization: token &amp;lt;personal token&amp;gt;' '&amp;lt;https://api.github.com/repos/&amp;gt;&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Enterprise:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -XGET -H 'authorization: token &amp;lt;personal token&amp;gt;' 'https://&amp;lt;GHE url&amp;gt;/api/v3/repos/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So for instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -XGET -H 'authorization: token '"${GHBOT_TOKEN}" https://api.github.com/repos/MyOrg/MyRepo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So write a quick script to use as a step in your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Checking GitHub Token for expiry and/or access"&lt;/span&gt;
&lt;span class="c"&gt;# prepend some arguments, but pass on whatever arguments this script was called with&lt;/span&gt;
&lt;span class="nv"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="s1"&gt;'\n%{http_code}\n'&lt;/span&gt;  &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--fail&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type:application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept:application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'authorization: token '&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GHBOT_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://api.github.com/repos/MyOrg/MyRepo &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;return_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; 0 &lt;span class="nt"&gt;-eq&lt;/span&gt; &lt;span class="nv"&gt;$return_code&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# remove the "http_code" line from the end of the output, and parse it&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Yay! Your personal access code is not expired and has access to this repo"&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Boo! Your personal access code is expired or has no access"&lt;/span&gt;
   &lt;span class="c"&gt;# comment the next line if you don't want to see the HTTP status code and return messages from GitHub&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Failure: code=&lt;/span&gt;&lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
   &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you can put this in your GitHub Action workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    steps:
      - name: Check Personal Access Token
        run: |
          echo "Checking GitHub Token for expiry and/or access"
          # prepend some arguments, but pass on whatever arguments this script was called with
          output="$(curl -XGET \
          --write '\n%{http_code}\n'  \
          --silent \
          --fail -H 'Content-Type:application/json' \
          -H 'Accept:application/json' \
          -H 'authorization: token '"${GHBOT_TOKEN}" https://api.github.com/repos/MyOrg/MyRepo \
          )"
          return_code=$?
          if [ 0 -eq $return_code ]; then
              # remove the "http_code" line from the end of the output, and parse it
              echo "Yay! Your personal access code is not expired and has access to this repo"
          else
              echo "Boo! Your personal access code is expired or has no access"
             # comment the next line if you don't want to see the HTTP status code and return messages from GitHub
             echo "Failure: code=$output"
             exit 1
          fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using the default GitHub token, change the above &lt;code&gt;${GHBOT_TOKEN}&lt;/code&gt; (which is sourced by from a shell environment variable you set in your action) to instead be &lt;code&gt;${{ secrets.GITHUB_TOKEN }}&lt;/code&gt; which the action will replace before it passes the script to the shell.&lt;/p&gt;

&lt;p&gt;Or write your own layers of JavaScript to check if the token is expired. You do you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus content
&lt;/h3&gt;

&lt;p&gt;Setting your git config to use a the token in a GitHub action can be done with the token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      - name: Setup git config via HTTPS using token
        run: |
          echo "Configuring git for HTTPS using token"
          git config --global user.name "github-actions[bot]"
          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --global url."https://oauth2:${{ secrets.GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or you can decide this whole token/HTTPS thing is dumb and use SSH keys (like a &lt;a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys#deploy-keys" rel="noopener noreferrer"&gt;deploy key&lt;/a&gt;). &lt;strong&gt;Warning:&lt;/strong&gt; GitHub will mark any commits as being authored by the same user who created the deploy key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: My cool workflow
env:
  SSH_AUTH_SOCK: /tmp/ssh_agent.sock
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
  cooljobnamehere:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
    - name: Setup Git with SSH Keys and known_hosts
      run: |
        ssh-agent -a "${SSH_AUTH_SOCK}" &amp;gt; /dev/null
        ssh-add - &amp;lt;&amp;lt;&amp;lt; "${{ secrets.MY_SSH_PRIVATE_KEY }}"
        echo "Configuring git..."
        git config --global user.name "github-actions[bot]"
        git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
        git config --global url."git@github.com:".insteadOf "https://github.com/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes when you use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "${{ secrets.MY_SSH_PRIVATE_KEY }}" | ssh-add - &amp;gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will sometimes get a failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error loading key "(stdin)": error in libcrypto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might have some carriage returns in there so try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "${{ secrets.MY_SSH_PRIVATE_KEY }}" | tr -d '\r' | ssh-add - &amp;gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also for some reason(?), this appears more reliable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-add - &amp;lt;&amp;lt;&amp;lt; "${{ secrets.MY_SSH_PRIVATE_KEY }}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Testing your SSH Deploy Key
&lt;/h5&gt;

&lt;p&gt;You likely have your own developer SSH config and key, but if you want to verify an SSH Deploy key (or a bot GitHub account SSH key):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -F /dev/null -o"IdentitiesOnly=yes" -o"StrictHostKeyChecking=no" -i id_ed25519 -T git@github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just ensure that the key and its directory have proper permissions!&lt;/p&gt;

&lt;p&gt;Typically you want the permissions to be:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Sample&lt;/th&gt;
&lt;th&gt;Numeric&lt;/th&gt;
&lt;th&gt;Bitwise&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSH folder&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.ssh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;700&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;drwx------&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public key&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.ssh/id_rsa.pub&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;644&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-rw-r--r--&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private key&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.ssh/id_rsa&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;600&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-rw-------&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Home folder&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;755&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;drwxr-xr-x&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So when you are in the deploy key directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod 700 .
chmod 600 id_ed25519
chmod 600 id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>githubactions</category>
      <category>github</category>
    </item>
    <item>
      <title>Comparison golang stacktrace error library output</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Wed, 24 Mar 2021 18:06:03 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/comparison-golang-stacktrace-error-library-output-2fph</link>
      <guid>https://dev.to/stevenacoffman/comparison-golang-stacktrace-error-library-output-2fph</guid>
      <description>&lt;p&gt;Note: &lt;a href="https://github.com/StevenACoffman/comparerr"&gt;Runnable examples are available here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Golang is great. I mostly love it. However, collecting an error with relevant context and a nicely formatted stacktrace is kind of a mess of competing approaches. This &lt;a href="https://github.com/cockroachdb/errors#features"&gt;this feature comparison&lt;/a&gt; is a good overview of the landscape.&lt;/p&gt;

&lt;p&gt;I wanted to compare the output of a few different "nicely formatted" approaches with two different kinds of wrapped&lt;br&gt;
errors, Sentinel errors and custom error types.&lt;/p&gt;

&lt;p&gt;Some libraries capture the exact state of the stack when an error happens, including every function call. &lt;/p&gt;

&lt;p&gt;Some try to attach relevant contextual information (messages, variables) at strategic places along the call stack,&lt;br&gt;
keeping stack traces compact and maximally useful.&lt;/p&gt;

&lt;p&gt;Some eliminate stacktrace duplication from wrapped errors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/jba/errfmt"&gt;jba/errfmt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  {Msg:This is a message Detail:Important detail Err:reading "file"
    cmd/prog/reader.go:122
  parsing line 23
    iff x &amp;gt; 3 {
    cmd/prog/parser.go:85
  syntax error
    cmd/prog/parser.go:214
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/emperror/emperror"&gt;emperror&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  something went wrong
  main.foo
  /Users/steve/Documents/git/comparerr/emperror/main.go:23
  main.main
  /Users/steve/Documents/git/comparerr/emperror/main.go:31
  runtime.main
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:203
  runtime.goexit
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/asm_amd64.s:1373

  starting bar

  something went wrong
  got an error in bar
  main.bar
  /Users/steve/Documents/git/comparerr/emperror/main.go:27
  main.main
  /Users/steve/Documents/git/comparerr/emperror/main.go:39
  runtime.main
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:203
  runtime.goexit
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/asm_amd64.s:1373 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/cockroachdb/errors"&gt;cockroachdb/errors&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Sentinel Something Went Wrong
  (1) attached stack trace
  -- stack trace:
  | main.foo
  |     /Users/steve/Documents/git/comparerr/cockroachdb-errors/main.go:23
  | main.main
  |     /Users/steve/Documents/git/comparerr/cockroachdb-errors/main.go:31
  | [...repeated from below...]
  Wraps: (2) attached stack trace
  -- stack trace:
  | main.init
  |     /Users/steve/Documents/git/comparerr/cockroachdb-errors/main.go:10
  | runtime.doInit
  |     /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:5480
  | runtime.main
  |     /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:190
  | runtime.goexit
  |     /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/asm_amd64.s:1373
  Wraps: (3) Sentinel Something Went Wrong
  Error types: (1) *withstack.withStack (2) *withstack.withStack (3) *errutil.leafError


  starting bar

  got an error in bar: bar something went wrong
  (1) attached stack trace
  -- stack trace:
  | main.bar
  |     /Users/steve/Documents/git/comparerr/cockroachdb-errors/main.go:27
  | main.main
  |     /Users/steve/Documents/git/comparerr/cockroachdb-errors/main.go:39
  | runtime.main
  |     /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:203
  | runtime.goexit
  |     /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/asm_amd64.s:1373
  Wraps: (2) got an error in bar
  Wraps: (3) bar something went wrong
  Error types: (1) *withstack.withStack (2) *errutil.withPrefix (3) main.ErrMyError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/palantir/stacktrace"&gt;palantir/stacktrace&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  foo returned error
  --- at /Users/steve/Documents/git/comparerr/palantir-err/main.go:22 (foo) ---
  Caused by: Sentinel Something Went Wrong
  --- at /Users/steve/Documents/git/comparerr/palantir-err/main.go:9 (init) ---


  starting bar

  got an error in bar
  --- at /Users/steve/Documents/git/comparerr/palantir-err/main.go:26 (bar) ---
  Caused by: bar something went wrong
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/pkg/errors"&gt;pkg/errors&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Sentinel Something Went Wrong
  main.init
  /Users/steve/Documents/git/comparerr/pkg-errors/main.go:10
  runtime.doInit
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:5480
  runtime.main
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:190
  runtime.goexit
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/asm_amd64.s:1373
  main.foo
  /Users/steve/Documents/git/comparerr/pkg-errors/main.go:23
  main.main
  /Users/steve/Documents/git/comparerr/pkg-errors/main.go:31
  runtime.main
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:203
  runtime.goexit
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/asm_amd64.s:1373


  starting bar

  bar something went wrong
  got an error in bar
  main.bar
  /Users/steve/Documents/git/comparerr/pkg-errors/main.go:27
  main.main
  /Users/steve/Documents/git/comparerr/pkg-errors/main.go:39
  runtime.main
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/proc.go:203
  runtime.goexit
  /Users/steve/.asdf/installs/golang/1.14.15/go/src/runtime/asm_amd64.s:1373
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can clone this repo, cd into any of the directories and run &lt;code&gt;go run main.go&lt;/code&gt; to see an example&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://play.golang.org/p/aYhdnfLSk8g"&gt;jba errfmt output example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.golang.org/p/OUrwpogR8_E"&gt;emperror output example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;cockroackdb/errors playground times out? Not sure what that is all about.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.golang.org/p/TwKMNrVrqE8"&gt;pkg/errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://play.golang.org/p/YCdTHCXEd0C"&gt;palantir/stacktrace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Don't Panic: Catching Panics in Errgroup Goroutines</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Sat, 16 Jan 2021 16:04:37 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/don-t-panic-catching-panics-in-errgroup-5hn</link>
      <guid>https://dev.to/stevenacoffman/don-t-panic-catching-panics-in-errgroup-5hn</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F0%2F00%2FThe_Hitchhiker%2527s_Guide_to_the_Galaxy%252C_english.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F0%2F00%2FThe_Hitchhiker%2527s_Guide_to_the_Galaxy%252C_english.svg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image by &lt;a href="https://commons.wikimedia.org/wiki/File:The_Hitchhiker%27s_Guide_to_the_Galaxy,_english.svg" rel="noopener noreferrer"&gt;Nicosmos&lt;/a&gt;, Public domain, via Wikimedia Commons&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  TL;DR version
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/StevenACoffman/errgroup" rel="noopener noreferrer"&gt;StevenACoffman/errgroup&lt;/a&gt; is a drop-in alternative to Go's wonderful &lt;a href="https://pkg.go.dev/golang.org/x/sync/errgroup" rel="noopener noreferrer"&gt;&lt;code&gt;sync/errgroup&lt;/code&gt;&lt;/a&gt; but it converts goroutine panics to errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why you want this
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;net/http&lt;/code&gt; installs a panic handler with each request-serving goroutine,&lt;br&gt;
goroutines &lt;strong&gt;do not&lt;/strong&gt; and &lt;strong&gt;cannot&lt;/strong&gt; inherit panic handlers from parent goroutines,&lt;br&gt;
so a &lt;code&gt;panic()&lt;/code&gt; in one of the child goroutines will kill the whole program.&lt;/p&gt;

&lt;p&gt;So in production, whenever you use an &lt;code&gt;sync.errgroup&lt;/code&gt;, you have to have the discipline to always remember to add a&lt;br&gt;
deferred &lt;code&gt;recover()&lt;/code&gt; to the beginning of &lt;strong&gt;every&lt;/strong&gt; new goroutine, and convert any panics to errors. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FromPanicValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}()&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can see this in &lt;a href="https://play.golang.org/p/WNgEPAQgbWg" rel="noopener noreferrer"&gt;action in the Go Playground here&lt;/a&gt;. You also want to be careful not to lose the stack trace. The &lt;code&gt;CollectStack&lt;/code&gt; function I used here is overly simplified, so it adds a little noise because it doesn't the skip the &lt;code&gt;FromPanicValue&lt;/code&gt; and &lt;code&gt;CollectStack&lt;/code&gt; frames. 🤷‍♂️&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;FromPanicValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"panic: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CollectStack&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"panic in errgroup goroutine %w&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CollectStack&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unknown panic: %+v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CollectStack&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CollectStack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A co-worker of mine co-worker &lt;a href="https://github.com/benjaminjkraft" rel="noopener noreferrer"&gt;Ben Kraft&lt;/a&gt;, wrote some handy wrapper code around &lt;code&gt;sync/errgroup&lt;/code&gt; to &lt;em&gt;avoid&lt;/em&gt; that boilerplate (and &lt;em&gt;required&lt;/em&gt; discipline). With his permission, I &lt;a href="https://github.com/StevenACoffman/errgroup" rel="noopener noreferrer"&gt;lightly modified it&lt;/a&gt; to&lt;br&gt;
lift it out of our private work repository for the more general Go community.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;StevenACoffman/errgroup&lt;/code&gt; is a drop-in alternative to Go's wonderful&lt;br&gt;
&lt;a href="https://pkg.go.dev/golang.org/x/sync/errgroup" rel="noopener noreferrer"&gt;&lt;code&gt;sync/errgroup&lt;/code&gt;&lt;/a&gt; with the difference that it converts goroutine panics to errors. &lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://play.golang.org/p/S8Gmr_sWZIi" rel="noopener noreferrer"&gt;see it in use in the playground&lt;/a&gt; or here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/StevenACoffman/errgroup"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errgroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"http://www.golang.org/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"http://www.google.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"http://www.somestupidname.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Launch a goroutine to fetch the URL.&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="c"&gt;// https://golang.org/doc/faq#closures_and_goroutines&lt;/span&gt;
        &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="c"&gt;// deliberate index out of bounds triggered&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetching:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// Wait for all HTTP fetches to complete.&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Successfully fetched all URLs."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Counterpoint
&lt;/h3&gt;

&lt;p&gt;There is &lt;a href="https://github.com/oklog/run/issues/10" rel="noopener noreferrer"&gt;an interesting discussion&lt;/a&gt; which has an alternative view that,&lt;br&gt;
with few exceptions, panics &lt;strong&gt;should&lt;/strong&gt; crash your program. I'm ok with that in development and testing, but would rather sleep soundly at night.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prior Art
&lt;/h3&gt;

&lt;p&gt;With only a cursory search, I found a few existing open source examples.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;a href="https://github.com/go-kratos/kratos" rel="noopener noreferrer"&gt;Kratos&lt;/a&gt; errgroup
&lt;/h4&gt;

&lt;p&gt;Kratos Go framework for microservices has a similar &lt;a href="https://github.com/go-kratos/kratos/blob/master/pkg/sync/errgroup/errgroup.go" rel="noopener noreferrer"&gt;errgroup&lt;/a&gt;&lt;br&gt;
solution.&lt;/p&gt;
&lt;h4&gt;
  
  
  PanicGroup by Sergey Alexandrovich
&lt;/h4&gt;

&lt;p&gt;In the article &lt;a href="https://evilmartians.com/chronicles/errors-in-go-from-denial-to-acceptance" rel="noopener noreferrer"&gt;Errors in Go:&lt;br&gt;
From denial to acceptance&lt;/a&gt;, &lt;br&gt;
(which advocates panic based flow control 😱), they have a &lt;code&gt;PanicGroup&lt;/code&gt; that's roughly equivalent:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;PanicGroup&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;wg&lt;/span&gt;      &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;
  &lt;span class="n"&gt;errOnce&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Once&lt;/span&gt;
  &lt;span class="n"&gt;err&lt;/span&gt;     &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;PanicGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;PanicGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c"&gt;// We need only the first error, sync.Once is useful here.&lt;/span&gt;
          &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errOnce&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I would appreciate feedback or suggestions for improvement!&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Tripperwares: http.Client Middleware - chaining RoundTrippers</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Wed, 22 Jul 2020 00:36:30 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/tripperwares-http-client-middleware-chaining-roundtrippers-3o00</link>
      <guid>https://dev.to/stevenacoffman/tripperwares-http-client-middleware-chaining-roundtrippers-3o00</guid>
      <description>&lt;p&gt;&lt;code&gt;http.Client&lt;/code&gt; Middleware or "Tripperwares" adhere to &lt;code&gt;func (http.RoundTripper) http.RoundTripper&lt;/code&gt; signature, hence the name. As such they are used as a Transport for &lt;code&gt;http.Client&lt;/code&gt;, and can wrap other transports.&lt;/p&gt;

&lt;p&gt;This means that the composition is purely net/http-based. &lt;/p&gt;

&lt;p&gt;While &lt;strong&gt;server&lt;/strong&gt; middleware is pretty commonly discussed (and there's lots to pick from), I found the documentation a little sparse on how (and why) to do this for clients. Luckily the amazing @PeterBourgon helped me on the &lt;code&gt;#gophers&lt;/code&gt; slack.&lt;/p&gt;

&lt;p&gt;A couple good use cases for TripperWare are request logging, caching, and certain kinds of auth (e.g. bearer token renewal).&lt;/p&gt;

&lt;p&gt;However, instead of making one complicated Tripperware to do all this, it's nice to compose several more tightly focussed ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/base64"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// thanks to peter bourgon on Gophers slack&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;jiraUserID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpandEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${JIRA_USERID}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;jiraPassword&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpandEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${JIRA_API_TOKEN}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;jiraBaseURL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"https://example.atlassian.net/rest/agile/1.0/board/"&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultTransport&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NewDelayRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NewLoggingRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hrt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewHeaderRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hrt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BasicAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jiraUserID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jiraPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hrt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jiraBaseURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"err=%v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;DelayRoundTripper&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt;  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewDelayRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;DelayRoundTripper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;DelayRoundTripper&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;DelayRoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;LoggingRoundTripper&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;
    &lt;span class="n"&gt;dst&lt;/span&gt;  &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewLoggingRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;LoggingRoundTripper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;LoggingRoundTripper&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;LoggingRoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"method=%s host=%s status_code=%d err=%v took=%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HeaderRoundTripper&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;
    &lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewHeaderRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HeaderRoundTripper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultTransport&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;HeaderRoundTripper&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HeaderRoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HeaderRoundTrip"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HeaderRoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;BasicAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;":"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;
    &lt;span class="n"&gt;base64Auth&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Basic "&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;base64Auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;HeaderRoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;rt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This works fine, but if you're less into receivers and prefer functional composition, here's an functional alternative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// NewRoundTripper returns an http.RoundTripper that is tooled for use in the app&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewRoundTripper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTripper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultTransport&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;roundTripperFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;roundTripperFunc&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="n"&gt;roundTripperFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RoundTrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I found that code in &lt;a href="https://github.com/evepraisal/go-evepraisal/blob/master/evepraisal/app.go#L320-L340"&gt;evepraisal/go-evepraisal&lt;/a&gt; where &lt;a class="comment-mentioned-user" href="https://dev.to/sudorandom"&gt;@sudorandom&lt;/a&gt;
 used it to instrument external calls with New Relic monitoring.&lt;/p&gt;

&lt;p&gt;Also, I found that there's a several tripperwares and some helper libraries here: &lt;a href="https://github.com/improbable-eng/go-httpwares"&gt;improbable-eng/go-httpwares&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's worth mentioning that changing the request is a bit of a no-no. Cloning the request, modifying that, and then passing that on is a workaround&lt;/p&gt;

&lt;p&gt;Here's &lt;a href="https://developer20.com/add-header-to-every-request-in-go/"&gt;an article about an alternative&lt;/a&gt; for header manipulation.&lt;/p&gt;

</description>
      <category>go</category>
      <category>http</category>
      <category>client</category>
    </item>
    <item>
      <title>When Now is not the time (in Go)</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Tue, 21 Jul 2020 22:10:29 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/when-now-is-not-the-time-in-go-2kki</link>
      <guid>https://dev.to/stevenacoffman/when-now-is-not-the-time-in-go-2kki</guid>
      <description>&lt;p&gt;When testing two &lt;code&gt;time.Time&lt;/code&gt; variables for equality in Go, you may find that there are (&lt;em&gt;ahem&lt;/em&gt;) &lt;strong&gt;times&lt;/strong&gt; when you get surprising results if you don't use &lt;code&gt;time.Equal()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;nowUTC&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"now:   "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"15:04"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nowUTC:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nowUTC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nowUTC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"15:04"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-----"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"now == nowUTC:     %t&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;nowUTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"now.Equal(nowUTC): %t&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nowUTC&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Output:&lt;/span&gt;
&lt;span class="c"&gt;// now:    Local 23:00&lt;/span&gt;
&lt;span class="c"&gt;// nowUTC: UTC   23:00&lt;/span&gt;
&lt;span class="c"&gt;// -----&lt;/span&gt;
&lt;span class="c"&gt;// now == nowUTC:     false&lt;/span&gt;
&lt;span class="c"&gt;// now.Equal(nowUTC): true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Run it in the &lt;a href="https://play.golang.org/p/7jF3XzWEmV4"&gt;Go Playground&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Handling Go modules replacement directive version lag and fork maintenance</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Thu, 11 Jun 2020 15:11:22 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/handling-go-modules-replacement-directive-version-lag-and-fork-maintenance-3npj</link>
      <guid>https://dev.to/stevenacoffman/handling-go-modules-replacement-directive-version-lag-and-fork-maintenance-3npj</guid>
      <description>&lt;p&gt;At Khan Academy, we have a &lt;a href="https://github.com/Khan/golangci-lint"&gt;public fork&lt;/a&gt; of a &lt;a href="https://github.com/golangci/golangci-lint"&gt;public repo&lt;/a&gt;, which we are carrying a patch for in a &lt;a href="https://github.com/Khan/golangci-lint/tree/custom-autofix"&gt;branch&lt;/a&gt;. The latest release tag in the upstream is 1.27.0&lt;/p&gt;

&lt;p&gt;In our other repositories, we have a replace directive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;replace github.com/golangci/golangci-lint =&amp;gt; github.com/Khan/golangci-lint v1.21.1-0.20200609223024-b6a17227ca75
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Even though the date and git commit SHA1 portion were correct, we found the pseudo-version prefix of &lt;code&gt;v1.21.1&lt;/code&gt; continuing to lag the upstream &lt;code&gt;v1.27.0&lt;/code&gt; confusing, and we were wondering what actions we could do in our public fork to be able to get that to update without running afoul of the dreaded "version before v1.27.0 would have negative patch number" or "unknown revision" errors.&lt;/p&gt;

&lt;p&gt;It turns out that it was because the branch with our patch was &lt;strong&gt;merging&lt;/strong&gt; upstream changes, rather than &lt;strong&gt;rebasing&lt;/strong&gt;. This meant the last common tag between upstream and our fork's branch &lt;em&gt;was&lt;/em&gt; &lt;code&gt;v1.21.1&lt;/code&gt;, not &lt;code&gt;v1.27.0&lt;/code&gt;. Darn computers always just doing what you asked them, right?&lt;/p&gt;

&lt;p&gt;So to fix this we had to rebase against the upstream's master's up to the last release commit (we don't want to use unreleased code). This is a little tricky to get correct, so here's the recipe in glorious bash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export WORKING_BRANCH=custom-autofix
export REBASED_BRANCH=autofix-custom
git clone git@github.com:Khan/golangci-lint.git
cd golangci-lint
git remote add upstream git@github.com:golangci/golangci-lint.git
git pull upstream master
git fetch --tags upstream
# Push the tags from my local to our fork's master branch (not sure if required)
git push -f --tags origin master
git checkout $WORKING_BRANCH
git co -b $REBASED_BRANCH
# Rebase against upstream latest tag
git reset --soft $(git log $(git tag | sort -V | tail -1) -1 --pretty=%H)
git commit -s -S -a
git push origin $REBASED_BRANCH -u
export LATEST_GOLANGCI_COMMIT=$(git log -1 --pretty=%H)
# Ta-da! All done. Now go to our private repo that uses this:
cd ~/khan/privaterepo
go mod edit -replace=github.com/golangci/golangci-lint=github.com/khan/golangci-lint@${LATEST_GOLANGCI_COMMIT}
# This will create/update the replace directive to github.com/Khan/golangci-lint v1.27.2-0.20200610182323-d6b2676ecc9b
# I could also have used the branch name instead of commit SHA1.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  WTF was all that about?
&lt;/h3&gt;

&lt;p&gt;Now there's some stuff in that bash-fu to unpack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git tag | sort -V | tail -1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will give the latest tag, which also will be upstream's latest tag unless you've tagged a release in your fork with a semantic version that's later (tip: &lt;em&gt;Don't&lt;/em&gt; do that).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git log $(git tag | sort -V | tail -1) -1 --pretty=%H
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will give the full commit SHA1 of the latest tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait, why does is it start with &lt;code&gt;v1.27.2&lt;/code&gt; instead of &lt;code&gt;v1.27.0&lt;/code&gt; or &lt;code&gt;v1.27.1&lt;/code&gt; ?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;go mod&lt;/code&gt; will take the fork's latest release tag's semver and increment the patch number by one (because your replacement fork &lt;em&gt;is&lt;/em&gt; a patch, see?).&lt;/p&gt;

&lt;p&gt;The incrementing the semver prefix is the only thing wrong with calculating it by hand in your fork using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "$(git tag | sort -V | tail -1)-$(git --no-pager show\
  --quiet\
  --abbrev=12\
  --date='format-local:%Y%m%d%H%M%S'\
  --format="%cd-%h")"
# output: v1.27.1-20200609183024-b6a17227ca75
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To test things out, I made a newer release tag than upstream in our fork, &lt;code&gt;v1.27.1&lt;/code&gt;, and &lt;code&gt;go mod&lt;/code&gt; chose &lt;code&gt;v1.27.2&lt;/code&gt; which tells me it's not just looking at upstream's tags. It seems less confusing to me to just keep syncing upstream's tags to your fork.&lt;/p&gt;

&lt;p&gt;Ok, I don't know if that is useful to anyone else!&lt;/p&gt;

</description>
      <category>go</category>
      <category>modules</category>
    </item>
    <item>
      <title>Grace - graceful shutdown made simple</title>
      <dc:creator>Steve Coffman</dc:creator>
      <pubDate>Sun, 05 Jan 2020 00:41:42 +0000</pubDate>
      <link>https://dev.to/stevenacoffman/grace-graceful-shutdown-made-simple-39lc</link>
      <guid>https://dev.to/stevenacoffman/grace-graceful-shutdown-made-simple-39lc</guid>
      <description>&lt;p&gt;I wrote a tiny library called &lt;a href="https://github.com/StevenACoffman/grace"&gt;&lt;em&gt;&lt;strong&gt;Grace&lt;/strong&gt;&lt;/em&gt;&lt;/a&gt; to gracefully shutdown your application by catching the OS signals using &lt;a href="https://godoc.org/golang.org/x/sync/errgroup"&gt;&lt;code&gt;sync.errgroup&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I often find I have invoked one or more persistent blocking methods, and some other method is needed be invoked in another goroutine to tell it to gracefully shut down when an interrupt is received.&lt;/p&gt;

&lt;p&gt;For instance, when &lt;a href="https://golang.org/pkg/net/http/#ListenAndServe"&gt;&lt;code&gt;ListenAndServe()&lt;/code&gt;&lt;/a&gt; is invoked, &lt;a href="https://godoc.org/net/http#Server.Shutdown"&gt;&lt;code&gt;Shutdown&lt;/code&gt;&lt;/a&gt; needs to be called.&lt;/p&gt;

&lt;p&gt;This library allows you to start zero or more concurrent goroutines, and trigger a graceful shutdown when an interrupt is received. You can bring your own cleanup function to listen for the context's Done channel to be closed, so it will work flexibly in a variety of scenarios. &lt;/p&gt;

&lt;p&gt;For example, you probably only want your cleanup function to close your database connection &lt;em&gt;after&lt;/em&gt; you have drained your http connections or grpc connections.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        func() error { 
            //cleanup: on interrupt, shutdown server
            &amp;lt;-ctx.Done()
            // We received an interrupt signal, shut down.
            if err := srv.Shutdown(ctx); err != nil {
            // Error from closing listeners, or context timeout:
                log.Printf("HTTP server Shutdown error: %v", err)
            }
            return db.Close()
        })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go &lt;code&gt;net/http&lt;/code&gt; package offers &lt;a href="https://godoc.org/net/http#Server.Shutdown"&gt;&lt;code&gt;Shutdown&lt;/code&gt;&lt;/a&gt; function to gracefully shutdown your http server.&lt;/li&gt;
&lt;li&gt;Go &lt;code&gt;database/sql&lt;/code&gt; package offers &lt;a href="https://godoc.org/database/sql#DB.Close"&gt;&lt;code&gt;Close&lt;/code&gt;&lt;/a&gt; function to gracefully close the connection to your SQL database. &lt;/li&gt;
&lt;li&gt;Google &lt;code&gt;google.golang.org/grpc&lt;/code&gt; package offers &lt;a href="https://godoc.org/google.golang.org/grpc#Server.GracefulStop"&gt;&lt;code&gt;Server.GracefulStop&lt;/code&gt;&lt;/a&gt;, stops accepting new connections, and blocks until all the pending RPCs are finished&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alternatively, this library also allows you to invoke zero or more concurrent goroutines with an optional timeout. &lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://goreportcard.com/report/github.com/StevenACoffman/grace"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a9KyGkOf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://goreportcard.com/badge/github.com/StevenACoffman/grace" alt=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://godoc.org/github.com/StevenACoffman/grace"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qk1M92g9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://godoc.org/github.com/StevenACoffman/grace%3Fstatus.svg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/StevenACoffman/grace
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;
&lt;h4&gt;
  
  
  Simple Run until Interrupt signal received
&lt;/h4&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/StevenACoffman/grace"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;grace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitWithFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ticker 2s ticked&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="c"&gt;// testcase what happens if an error occured&lt;/span&gt;
                &lt;span class="c"&gt;//return fmt.Errorf("test error ticker 2s")&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"closing ticker 2s goroutine&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"finished clean"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Usage with a default timeout:
&lt;/h4&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/StevenACoffman/grace"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;grace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitWithTimeoutAndFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ticker 2s ticked&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="c"&gt;// testcase what happens if an error occured&lt;/span&gt;
                &lt;span class="c"&gt;//return fmt.Errorf("test error ticker 2s")&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"closing ticker 2s goroutine&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"finished clean"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Usage with cleanup on shutdown
&lt;/h4&gt;

&lt;p&gt;Bring your own cleanup function!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/StevenACoffman/grace"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;grace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;httpServer&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;

    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitWithFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;healthCheck&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;httpServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newHTTPServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;httpServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrServerClosed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
            &lt;span class="c"&gt;//cleanup: on interrupt, shutdown server&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"closing http goroutine&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;httpServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"finished clean"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;healthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;newHTTPServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;httpServer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;ReadTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;WriteTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP Metrics server serving at %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;httpServer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Prior Art and Alternatives
&lt;/h3&gt;

&lt;p&gt;This library uses errgroup, but I found a number of other libraries that use other mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/vrecan/death"&gt;death&lt;/a&gt; (sync.WaitGroup)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/TV4/graceful"&gt;graceful&lt;/a&gt; (context cancellation)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pseidemann/finish/"&gt;finish&lt;/a&gt; (context + mutexes)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/heartwilltell/waitabit"&gt;waitabit&lt;/a&gt; (sync.WaitGroup)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/pteich/c0bb58b0b7c8af7cc6a689dd0d3d26ef"&gt;Gist using errgroup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/akhenakh/38dbfea70dc36964e23acc19777f3869"&gt;Gist using GRPC GracefulStop&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comparing them is pretty instructive. I wish I'd used some of their testing techniques!&lt;/p&gt;

</description>
      <category>go</category>
      <category>shutdown</category>
      <category>graceful</category>
    </item>
  </channel>
</rss>
