Browse Source

Merge branch 'master' of github.com:leothelocust/website

dependabot/npm_and_yarn/ini-1.3.7
Levi Olson 4 years ago
parent
commit
7e31a3e189
16 changed files with 462 additions and 172 deletions
  1. +20
    -0
      Makefile
  2. +3
    -2
      app.js
  3. +17
    -38
      core.css
  4. +249
    -0
      package-lock.json
  5. +2
    -1
      package.json
  6. +26
    -0
      posts/.html
  7. +55
    -111
      posts/basic-http-routing-in-golang.html
  8. +13
    -13
      posts/basic-http-routing-in-golang.md
  9. +26
    -0
      posts/chrome-ext-better-network-panel.html
  10. +9
    -0
      posts/chrome-ext-better-network-panel.json
  11. +38
    -0
      posts/chrome-ext-better-network-panel.md
  12. BIN
      public/images/BNPforChromeScreenshot.png
  13. BIN
      public/images/BNPforChromeScreenshotOriginal.png
  14. BIN
      public/images/bnpscreenshot.png
  15. +2
    -2
      views/pages/about.html
  16. +2
    -5
      views/pages/index.ejs

+ 20
- 0
Makefile View File

@ -0,0 +1,20 @@
SHELL := /bin/bash
FILE := tmp.txt
CAT := bat
# ifeq (, $(shell which bat))
# $(error "No bat in $(PATH), consider installing it")
# CAT = cat
# endif
.PHONY : compile run
compile :
@ls -1 posts/ | sed -e "s/\..*$$//" | uniq | grep -v 'example'; \
read -p "Which post? " post; \
./node_modules/showdown/bin/showdown.js makehtml -i posts/$$post.md -o posts/$$post.html
run :
@echo "-> Running"
@npm run startdev

+ 3
- 2
app.js View File

@ -38,8 +38,9 @@ app.get('/posts', (req, res) => {
}
}
data.posts.sort(function (a, b) {
var keyA = new Date(a.created_at),
keyB = new Date(b.created_at)
console.log(new Date(a.created_at_short), new Date(b.created_at_short));
var keyA = new Date(a.created_at_short),
keyB = new Date(b.created_at_short)
// Compare the 2 dates
if (keyA < keyB) return 1
if (keyA > keyB) return -1

+ 17
- 38
core.css View File

@ -4,8 +4,8 @@ html {
body {
font-family: Hack, Menlo, Monaco, Ubuntu Mono, monospace;
font-size: 14px;
color: #ddd;
background: #222;
color: #222;
background: #ddd;
padding: 20px;
margin: 0;
display: flex;
@ -47,10 +47,11 @@ h6 {
}
blockquote {
border-left: 5px solid #ddd;
border-left: 5px solid #AAA;
margin: 0;
padding: 0 20px 0 20px;
margin: 0 0 40px 0;
padding: 10px 20px;
margin: 0 100px 25px 40px;
background-color: #CCC;
}
.container {
@ -77,24 +78,11 @@ div.site {
}
a {
color: #fff;
color: #222;
}
a:hover {
color: #999;
}
a:visited {
color: #eee;
}
code {
color: #ddd;
background: #333;
}
pre {
background: #333;
color: #F28;
}
.main-menu a,
@ -181,27 +169,24 @@ article li {
align-items: baseline;
}
code {
color: lightcoral;
}
pre>code {
color: #ddd;
img {
max-width: 100%;
}
pre {
padding: 20px 10px !important;
background: #CCC;
border: 1px solid #888;
border-radius: 4px;
}
pre code {
background: #CCC;
}
pre>code {
color: #333;
padding: 0px;
}
pre.prettyprint {
code {
color: #333;
background: #CCC;
padding: 2px 6px;
border-radius: 4px;
}
code.prettyprint {
@ -209,12 +194,6 @@ code.prettyprint {
padding: 2px 8px;
border-radius: 4px;
}
code {
color: #333;
background: #CCC;
padding: 2px 6px;
border-radius: 4px;
}
.mute {
color: #999;

+ 249
- 0
package-lock.json View File

@ -381,6 +381,45 @@
"integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
"dev": true
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -513,6 +552,12 @@
"ms": "2.0.0"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@ -616,6 +661,12 @@
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz",
"integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ=="
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -853,6 +904,15 @@
"unpipe": "~1.0.0"
}
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -1443,6 +1503,12 @@
}
}
},
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"get-func-name": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
@ -1831,6 +1897,16 @@
"package-json": "^4.0.0"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"lowercase-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
@ -2119,6 +2195,30 @@
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"dev": true
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"package-json": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
@ -2148,6 +2248,12 @@
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
"dev": true
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -2351,6 +2457,18 @@
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
"dev": true
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@ -2423,6 +2541,12 @@
"send": "0.16.2"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"set-value": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
@ -2466,6 +2590,15 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"showdown": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz",
"integrity": "sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==",
"dev": true,
"requires": {
"yargs": "^14.2"
}
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@ -2982,6 +3115,12 @@
"isexe": "^2.0.0"
}
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"widest-line": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
@ -2991,6 +3130,45 @@
"string-width": "^2.1.1"
}
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"write-file-atomic": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz",
@ -3008,11 +3186,82 @@
"integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
"dev": true
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"yargs": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
"integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
"dev": true,
"requires": {
"cliui": "^5.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^15.0.1"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"yargs-parser": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
"integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"dependencies": {
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
}
}
}
}
}

+ 2
- 1
package.json View File

@ -10,7 +10,8 @@
"devDependencies": {
"chai": "^4.1.2",
"nodemon": "^1.18.10",
"supertest": "^3.0.0"
"supertest": "^3.0.0",
"showdown": "^1.9.1"
},
"scripts": {
"start": "node app.js",

+ 26
- 0
posts/.html View File

@ -0,0 +1,26 @@
<h1 id="betternetworkpanelachromeextension">Better Network Panel - a Chrome extension</h1>
<blockquote>
<p>As a Salesforce and Vlocity developer, I'm constantly looking for ways to improve my workflow, speed up my debugging, and find answers fast.</p>
</blockquote>
<h2 id="theproblem">THE PROBLEM</h2>
<p>Over the last couple months, part of my debugging process has involved using the Chrome DevTools "Network" panel to find a specific <code>apexremote</code> call. The search to find one <code>apexremote</code> call out of dozens has been… annoying. </p>
<p>The page loads, several dozen <code>apexremote</code> calls flood the panel, and I start clicking each one, until the correct one (i.e. <code>Request-Body</code> contains "xyz") and I can proceed to look at the Preview.</p>
<p>The issue has only just begun, as I need to inspect the <code>Response</code>, perform some searches for <code>ID</code>s and the like, and although the <code>Response</code> is JSON format, the node in the response I need to search is stringified in a child member. So I must copy the data, parse it somehow, either locally on my machine or on the web (jsoneditoronline.org has been great) and finally perform the searching I need.</p>
<p>And all of the above is done several times a day.</p>
<h2 id="thesolution">THE SOLUTION</h2>
<p>I present to you a "Better Network Panel". A Chrome extension that adds a new panel, and offers great features like:</p>
<ul>
<li><strong>Full Search</strong> - Entire request is searchable (i.e. headers, postbody, etc…), not just URI</li>
<li><strong>JSON Parsing</strong> - Even nested members that contain stringified JSON are parsed</li>
<li><strong>JSON Search</strong> - Incremental searching is available directly in the Preview pane</li>
<li><strong>Import HAR</strong> - Import your own HAR file and use this tool for debugging</li>
<li><strong>Download HAR</strong> - Export a request as a HAR file and use an external tool for further debugging</li>
<li><strong>Regex Search</strong> - Powerfull regex searches can be performed on the Preview pane</li>
<li>More to come</li>
</ul>
<h2 id="opensourcebaby">Open source baby</h2>
<p>View it on <a href="https://github.com/leothelocust/better-network-chrome-panel">GitHub</a></p>
<h2 id="specialthanks">Special Thanks</h2>
<p>A huge thanks and recognition goes to <a href="https://github.com/milton-lai/saml-chrome-panel">Milton Lai</a> and his project SAML Chrome Panel. I started from a fork of his project, but later started fresh as there was a lot of SAML specific code that this project doesn't use/need. The UI is nearly identical, but the code underneath has become fairly different at this point.</p>
<p>The SAML Chrome Panel was a huge help and ispiration! Thank you Milton and contributors to the SAML Chrome Panel project!</p>
<p><img src="../public/images/BNPforChromeScreenshotOriginal.png" alt="BNP for Chrome" /></p>

+ 55
- 111
posts/basic-http-routing-in-golang.html View File

@ -1,170 +1,117 @@
<h1 id="basic-http-routing-in-golang">Basic HTTP Routing in Golang</h1>
<p>Golang is incredibly powerful. Its standard library has so much to offer, but I think other languages have encouraged the
use of external libraries for even the most basic tasks. For example, with JavaScript, most inexperienced developers
seem to use jQuery to do simple tasks like selecting an element and replacing its contents. When you and I both know
jQuery is way overkill for such a task.
<a href="https://leviolson.com/posts/vanilla-js-basics">See my article on Vanilla JS basics</a>.</p>
<p>I believe that in order to be considered an expert in a language, you must at least be able to demonstrate using the core
language to achieve your goal. In our current case, HTTP routing. Now to be clear, I don't think you need to write everything
from scratch all the time, but you should have a firm grasp on what is available by the core language, and when you are
better suited to use an external library. If you are looking for more advanced HTTP routing, then I would suggest using
something like
<a href="https://github.com/gin-gonic/gin">gin</a>.</p>
<h1 id="basichttproutingingolang">Basic HTTP Routing in Golang</h1>
<p>Golang is incredibly powerful. Its standard library has so much to offer, but I think other languages have encouraged the use of external libraries for even the most basic tasks. For example, with JavaScript, most inexperienced developers seem to use jQuery to do simple tasks like selecting an element and replacing its contents. When you and I both know jQuery is way overkill for such a task. <a href="https://leviolson.com/posts/vanilla-js-basics">See my article on Vanilla JS basics</a>.</p>
<p>I believe that in order to be considered an expert in a language, you must at least be able to demonstrate using the core language to achieve your goal. In our current case, HTTP routing. Now to be clear, I don't think you need to write everything from scratch all the time, but you should have a firm grasp on what is available by the core language, and when you are better suited to use an external library. If you are looking for more advanced HTTP routing, then I would suggest using something like <a href="https://github.com/gin-gonic/gin">gin</a>.</p>
<p>Enough ranting, let's get to it.</p>
<h2 id="assumptions">Assumptions</h2>
<p>I assume you have basic knowledge of the Go language at this point, so if not, it might be worth searching for some entry
level basics first. See
<a href="https://tour.golang.org">A Tour of Go</a>.</p>
<h2 id="lets-begin">Let's begin</h2>
<p>The accompanying repo for the code produced in this article is located
<a href="https://github.com/leothelocust/basic-http-routing-in-golang">on github</a>.</p>
<h3 id="step-1">Step 1</h3>
<p>I assume you have basic knowledge of the Go language at this point, so if not, it might be worth searching for some entry level basics first. See <a href="https://tour.golang.org">A Tour of Go</a>.</p>
<h2 id="letsbegin">Let's begin</h2>
<p>The accompanying repo for the code produced in this article is located <a href="https://github.com/leothelocust/basic-http-routing-in-golang">on github</a>.</p>
<h3 id="step1">Step 1</h3>
<p>Here is our basic folder structure for this basic http routing example:</p>
<pre><code> basic-http-routing-in-golang/
main.go
</code></pre>
<p>As a starting point our
<code>main.go</code> file looks like this:</p>
<pre class="prettyprint"><code class="language-go"> package main
<p>As a starting point our <code>main.go</code> file looks like this:</p>
<pre class="prettyprint"><code> package main
import (
&quot;fmt&quot;
_ &quot;net/http&quot;
"fmt"
_ "net/http"
)
func main() {
fmt.Println(&quot;Hello HTTP&quot;)
fmt.Println("Hello HTTP")
}
</code></pre>
<h3 id="step-2">Step 2</h3>
<p>Now starting at a very basic level, we can leverage the
<a href="https://golang.org/pkg/net/http/#HandleFunc">
<code>http.HandleFunc</code>
</a> method.</p>
<h3 id="step2">Step 2</h3>
<p>Now starting at a very basic level, we can leverage the <a href="https://golang.org/pkg/net/http/#HandleFunc"><code>http.HandleFunc</code></a> method.</p>
<p>It is very simple to use and its signature is easy to understand.</p>
<pre class="prettyprint"><code class="language-go"> func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
</code></pre>
<p>Which basically means,
<code>http.HandleFunc(&quot;/url&quot;, routingFunction)</code> where
<code>routingFunction</code> looks like this:</p>
<p>Which basically means, <code class="prettyprint">http.HandleFunc("/url", routingFunction)</code> where <code>routingFunction</code> looks like this:</p>
<pre class="prettyprint"><code class="language-go"> func routingFunction(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, &quot;Hello HTTP&quot;)
fmt.Fprint(w, "Hello HTTP")
}
</code></pre>
<p>With
<code class="prettyprint">fmt.Fprint()</code> we can pass an
<code class="prettyprint">http.ResponseWriter</code> and a message to display. Our browser will now look like this when we visit the
<code class="prettyprint">/url</code> endpoint.</p>
<p>
<img src="https://leviolson.com/images/step2-browser-output.png" alt="Browser Output for Step 2 - Hello HTTP">
</p>
<p>Here is what
<code class="prettyprint">main.go</code> looks like at this point:</p>
<p>With <code>fmt.Fprint()</code> we can pass an <code>http.ResponseWriter</code> and a message to display. Our browser will now look like this when we visit the <code>/url</code> endpoint.</p>
<p><img src="https://leviolson.com/images/step2-browser-output.png" alt="Browser Output for Step 2 - Hello HTTP" /></p>
<p>Here is what <code>main.go</code> looks like at this point:</p>
<pre class="prettyprint"><code class="language-go"> package main
import (
&quot;fmt&quot;
&quot;log&quot;
&quot;net/http&quot;
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc(&quot;/hello&quot;, helloHTTP)
log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
http.HandleFunc("/hello", helloHTTP)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func helloHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, &quot;Hello HTTP&quot;)
fmt.Fprint(w, "Hello HTTP")
}
</code></pre>
<p>Now we could stop there, as this is a &quot;basic&quot; http routing example, but I think it isn't quite useful as an example
yet, until we start to see something slightly more practical.</p>
<h3 id="step-3">Step 3</h3>
<p>So let's add a
<code>NotFound</code> page when we don't match a pattern in
<code>HandleFunc</code>. It's as simple as:</p>
<p>Now we could stop there, as this is a "basic" http routing example, but I think it isn't quite useful as an example yet, until we start to see something slightly more practical.</p>
<h3 id="step3">Step 3</h3>
<p>So let's add a <code>NotFound</code> page when we don't match a pattern in <code>HandleFunc</code>. It's as simple as:</p>
<pre class="prettyprint"><code class="language-go"> func notFound(w http.ResponseWriter, req *http.Request) {
http.NotFound(w, req)
}
</code></pre>
<p>Here is what
<code>main.go</code> looks like after that:</p>
<p>Here is what <code>main.go</code> looks like after that:</p>
<pre class="prettyprint"><code class="language-go"> package main
import (
&quot;fmt&quot;
&quot;log&quot;
&quot;net/http&quot;
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc(&quot;/hello&quot;, helloHTTP)
http.HandleFunc(&quot;/&quot;, notFound)
log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
http.HandleFunc("/hello", helloHTTP)
http.HandleFunc("/", notFound)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func helloHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, &quot;Hello HTTP&quot;)
fmt.Fprint(w, "Hello HTTP")
}
func notFound(w http.ResponseWriter, req *http.Request) {
http.NotFound(w, req)
}
</code></pre>
<p>This will match
<code>/hello</code> and use the
<code>HelloHTTP</code> method to print &quot;Hello HTTP&quot; to the browser. Any other URLs will get caught by the
<code>/</code> pattern and be given the
<code>http.NotFound</code> response to the browser.</p>
<p>This will match <code>/hello</code> and use the <code>HelloHTTP</code> method to print "Hello HTTP" to the browser. Any other URLs will get caught by the <code>/</code> pattern and be given the <code>http.NotFound</code> response to the browser.</p>
<p>So that works, but I think we can go further.</p>
<h3 id="step-4">Step 4</h3>
<p>We need to give ourselves something more specific than the simple contrived
<code>/hello</code> endpoint above. So let's assume we are needing to get a user profile. We will use the url
<code>/user/:id</code> where
<code>:id</code> is an identifier used to get the user profile from our persistance layer (i.e. our database).</p>
<p>We'll start by creating a new method for this GET request called
<code>userProfile</code>:</p>
<h3 id="step4">Step 4</h3>
<p>We need to give ourselves something more specific than the simple contrived <code>/hello</code> endpoint above. So let's assume we are needing to get a user profile. We will use the url <code>/user/:id</code> where <code>:id</code> is an identifier used to get the user profile from our persistance layer (i.e. our database).</p>
<p>We'll start by creating a new method for this GET request called <code>userProfile</code>:</p>
<pre class="prettyprint"><code class="language-go"> func userProfile(w http.ResponseWriter, req *http.Request) {
userID := req.URL.Path[len(&quot;/user/&quot;):]
fmt.Fprintf(w, &quot;User Profile: %q&quot;, userID)
userID := req.URL.Path[len("/user/"):]
fmt.Fprintf(w, "User Profile: %q", userID)
}
</code></pre>
<p>Notice that we get the URL from the
<code>req</code> variable and we treat the string returned from
<code>req.URL.Path</code> as a byte slice to get everything after the
<code>/user/</code> in the string.
<strong>Note: this isn't fool proof,
<code>/user/10ok</code> would get matched here, and we would be assigning
<code>userID</code> to
<code>&quot;10ok&quot;</code>.</strong>
</p>
<p>Let's add this new route in our
<code>main</code> function:</p>
<p>Notice that we get the URL from the <code>req</code> variable and we treat the string returned from <code>req.URL.Path</code> as a byte slice to get everything after the <code>/user/</code> in the string. <strong>Note: this isn't fool proof, <code>/user/10ok</code> would get matched here, and we would be assigning <code>userID</code> to <code>"10ok"</code>.</strong></p>
<p>Let's add this new route in our <code>main</code> function:</p>
<pre class="prettyprint"><code class="language-go"> func main() {
http.HandleFunc(&quot;/hello&quot;, helloHTTP)
http.HandleFunc(&quot;/user/&quot;, userProfile)
http.HandleFunc(&quot;/&quot;, notFound)
log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))
http.HandleFunc("/hello", helloHTTP)
http.HandleFunc("/user/", userProfile)
http.HandleFunc("/", notFound)
log.Fatal(http.ListenAndServe(":8080", nil))
}
</code></pre>
<p>
<em>Note: that this pattern
<code>/user/</code> matches the trailing
<code>/</code> so that a call to
<code>/user</code> in the browser would return a
<code>404 Not Found</code>.</em>
</p>
<h3 id="step-5">Step 5</h3>
<p>Ok, so we have introduced some pretty severe holes in the security of our new HTTP router. As mentioned in a note above,
treating the
<code>req.URL.Path</code> as a byte slice and just taking the last half is a terrible idea. So let's fix this:</p>
<pre class="prettyprint"><code class="language-go"> var validPath = regexp.MustCompile(&quot;^/(user)/([0-9]+)$&quot;)
<p><em>Note: that this pattern <code>/user/</code> matches the trailing <code>/</code> so that a call to <code>/user</code> in the browser would return a <code>404 Not Found</code>.</em></p>
<h3 id="step5">Step 5</h3>
<p>Ok, so we have introduced some pretty severe holes in the security of our new HTTP router. As mentioned in a note above, treating the <code>req.URL.Path</code> as a byte slice and just taking the last half is a terrible idea. So let's fix this:</p>
<pre class="prettyprint"><code class="language-go"> var validPath = regexp.MustCompile("^/(user)/([0-9]+)$")
func getID(w http.ResponseWriter, req *http.Request) (string, error) {
m := validPath.FindStringSubmatch(req.URL.Path)
if m == nil {
http.NotFound(w, req)
return &quot;&quot;, errors.New(&quot;Invalid ID&quot;)
return "", errors.New("Invalid ID")
}
return m[2], nil // The ID is the second subexpression.
}
@ -175,11 +122,8 @@
if err != nil {
return
}
fmt.Fprintf(w, &quot;User Profile: %q&quot;, userID)
fmt.Fprintf(w, "User Profile: %q", userID)
}
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>For now, I'm calling this &quot;Basic HTTP Routing in Golang&quot; article finished. But I do plan to add more to it as time
allows. Additionally, I'd like to create a more advanced article that discusses the ability to respond to not only GET
requests, but also POST, PUT, and DELETE HTTP methods. Look for an &quot;Advanced HTTP routing in Golang&quot; article
in the future. Thanks for reading this far. I wish you well in your Go endeavors.</p>
<p>For now, I'm calling this "Basic HTTP Routing in Golang" article finished. But I do plan to add more to it as time allows. Additionally, I'd like to create a more advanced article that discusses the ability to respond to not only GET requests, but also POST, PUT, and DELETE HTTP methods. Look for an "Advanced HTTP routing in Golang" article in the future. Thanks for reading this far. I wish you well in your Go endeavors.</p>

+ 13
- 13
posts/basic-http-routing-in-golang.md View File

@ -18,13 +18,13 @@ The accompanying repo for the code produced in this article is located [on githu
Here is our basic folder structure for this basic http routing example:
<pre class="prettyprint"><code> basic-http-routing-in-golang/
<pre><code> basic-http-routing-in-golang/
main.go
</pre></code>
</code></pre>
As a starting point our `main.go` file looks like this:
<pre class="prettyprint"><code class="language-go"> package main
<pre class="prettyprint"><code> package main
import (
"fmt"
@ -34,7 +34,7 @@ As a starting point our `main.go` file looks like this:
func main() {
fmt.Println("Hello HTTP")
}
</pre></code>
</code></pre>
### Step 2 ###
@ -43,14 +43,14 @@ Now starting at a very basic level, we can leverage the [`http.HandleFunc`](http
It is very simple to use and its signature is easy to understand.
<pre class="prettyprint"><code class="language-go"> func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
</pre></code>
</code></pre>
Which basically means, <code class="prettyprint">http.HandleFunc("/url", routingFunction)</code> where `routingFunction` looks like this:
<pre class="prettyprint"><code class="language-go"> func routingFunction(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, "Hello HTTP")
}
</pre></code>
</code></pre>
With `fmt.Fprint()` we can pass an `http.ResponseWriter` and a message to display. Our browser will now look like this when we visit the `/url` endpoint.
@ -74,7 +74,7 @@ Here is what `main.go` looks like at this point:
func helloHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, "Hello HTTP")
}
</pre></code>
</code></pre>
Now we could stop there, as this is a "basic" http routing example, but I think it isn't quite useful as an example yet, until we start to see something slightly more practical.
@ -85,7 +85,7 @@ So let's add a `NotFound` page when we don't match a pattern in `HandleFunc`. I
<pre class="prettyprint"><code class="language-go"> func notFound(w http.ResponseWriter, req *http.Request) {
http.NotFound(w, req)
}
</pre></code>
</code></pre>
Here is what `main.go` looks like after that:
@ -110,7 +110,7 @@ Here is what `main.go` looks like after that:
func notFound(w http.ResponseWriter, req *http.Request) {
http.NotFound(w, req)
}
</pre></code>
</code></pre>
This will match `/hello` and use the `HelloHTTP` method to print "Hello HTTP" to the browser. Any other URLs will get caught by the `/` pattern and be given the `http.NotFound` response to the browser.
@ -126,7 +126,7 @@ We'll start by creating a new method for this GET request called `userProfile`:
userID := req.URL.Path[len("/user/"):]
fmt.Fprintf(w, "User Profile: %q", userID)
}
</pre></code>
</code></pre>
Notice that we get the URL from the `req` variable and we treat the string returned from `req.URL.Path` as a byte slice to get everything after the `/user/` in the string. **Note: this isn't fool proof, `/user/10ok` would get matched here, and we would be assigning `userID` to `"10ok"`.**
@ -138,7 +138,7 @@ Let's add this new route in our `main` function:
http.HandleFunc("/", notFound)
log.Fatal(http.ListenAndServe(":8080", nil))
}
</pre></code>
</code></pre>
_Note: that this pattern `/user/` matches the trailing `/` so that a call to `/user` in the browser would return a `404 Not Found`._
@ -156,7 +156,7 @@ Ok, so we have introduced some pretty severe holes in the security of our new HT
}
return m[2], nil // The ID is the second subexpression.
}
</pre></code>
</code></pre>
Now we can use this method in our code:
@ -167,7 +167,7 @@ Now we can use this method in our code:
}
fmt.Fprintf(w, "User Profile: %q", userID)
}
</pre></code>
</code></pre>
## Conclusion ##

+ 26
- 0
posts/chrome-ext-better-network-panel.html View File

@ -0,0 +1,26 @@
<h1 id="betternetworkpanelachromeextension">Better Network Panel - a Chrome extension</h1>
<blockquote>
<p>"As a Salesforce and Vlocity developer, I'm constantly looking for ways to improve my workflow, speed up my debugging, and find answers fast."</p>
</blockquote>
<h2 id="theproblem">THE PROBLEM</h2>
<p>Over the last couple months, part of my debugging process has involved using the Chrome DevTools "Network" panel to find a specific <code>apexremote</code> call. The search to find one <code>apexremote</code> call out of dozens has been… annoying. </p>
<p>The page loads, several dozen <code>apexremote</code> calls flood the panel, and I start clicking each one, until the correct one (i.e. <code>Request-Body</code> contains "xyz") and I can proceed to look at the Preview.</p>
<p>The issue has only just begun, as I need to inspect the <code>Response</code>, perform some searches for <code>ID</code>s and the like, and although the <code>Response</code> is JSON format, the node in the response I need to search is stringified in a child member. So I must copy the data, parse it somehow, either locally on my machine or on the web (jsoneditoronline.org has been great) and finally perform the searching I need.</p>
<p>And all of the above is done several times a day.</p>
<h2 id="thesolution">THE SOLUTION</h2>
<p><img src="/images/bnpscreenshot.png" alt="BNP for Chrome" /></p>
<p>I present to you a "Better Network Panel". A Chrome extension that adds a new panel, and offers great features like:</p>
<ul>
<li><strong>Full Search</strong> - Entire request is searchable (i.e. headers, postbody, etc…), not just URI</li>
<li><strong>JSON Parsing</strong> - Even nested members that contain stringified JSON are parsed</li>
<li><strong>JSON Search</strong> - Incremental searching is available directly in the Preview pane</li>
<li><strong>Import HAR</strong> - Import your own HAR file and use this tool for debugging</li>
<li><strong>Download HAR</strong> - Export a request as a HAR file and use an external tool for further debugging</li>
<li><strong>Regex Search</strong> - Powerfull regex searches can be performed on the Preview pane</li>
<li>More to come</li>
</ul>
<h2 id="opensourcebaby">Open source baby</h2>
<p>View it on <a href="https://github.com/leothelocust/better-network-chrome-panel">GitHub</a></p>
<h2 id="specialthanks">Special Thanks</h2>
<p>A huge thanks and recognition goes to <a href="https://github.com/milton-lai/saml-chrome-panel">Milton Lai</a> and his project SAML Chrome Panel. I started from a fork of his project, but later started fresh as there was a lot of SAML specific code that this project doesn't use/need. The UI is nearly identical, but the code underneath has become fairly different at this point.</p>
<p>The SAML Chrome Panel was a huge help and ispiration! Thank you Milton and contributors to the SAML Chrome Panel project!</p>

+ 9
- 0
posts/chrome-ext-better-network-panel.json View File

@ -0,0 +1,9 @@
{
"title": "Better Network Panel - a Chrome Extension - Levi Olson",
"permalink": "/posts/chrome-ext-better-network-panel",
"created_at": "2020-10-04T17:55:13-33:00",
"created_at_short": "2020-10-04",
"post_title": "better network panel - a chrome extension",
"active": "posts",
"content_file": "chrome-ext-better-network-panel.html"
}

+ 38
- 0
posts/chrome-ext-better-network-panel.md View File

@ -0,0 +1,38 @@
# Better Network Panel - a Chrome extension #
> "As a Salesforce and Vlocity developer, I'm constantly looking for ways to improve my workflow, speed up my debugging, and find answers fast."
## THE PROBLEM
Over the last couple months, part of my debugging process has involved using the Chrome DevTools "Network" panel to find a specific `apexremote` call. The search to find one `apexremote` call out of dozens has been... annoying.
The page loads, several dozen `apexremote` calls flood the panel, and I start clicking each one, until the correct one (i.e. `Request-Body` contains "xyz") and I can proceed to look at the Preview.
The issue has only just begun, as I need to inspect the `Response`, perform some searches for `ID`s and the like, and although the `Response` is JSON format, the node in the response I need to search is stringified in a child member. So I must copy the data, parse it somehow, either locally on my machine or on the web (jsoneditoronline.org has been great) and finally perform the searching I need.
And all of the above is done several times a day.
## THE SOLUTION
![BNP for Chrome](/images/bnpscreenshot.png)
I present to you a "Better Network Panel". A Chrome extension that adds a new panel, and offers great features like:
* **Full Search** - Entire request is searchable (i.e. headers, postbody, etc...), not just URI
* **JSON Parsing** - Even nested members that contain stringified JSON are parsed
* **JSON Search** - Incremental searching is available directly in the Preview pane
* **Import HAR** - Import your own HAR file and use this tool for debugging
* **Download HAR** - Export a request as a HAR file and use an external tool for further debugging
* **Regex Search** - Powerfull regex searches can be performed on the Preview pane
* More to come
## Open source baby
View it on [GitHub](https://github.com/leothelocust/better-network-chrome-panel)
## Special Thanks
A huge thanks and recognition goes to [Milton Lai](https://github.com/milton-lai/saml-chrome-panel) and his project SAML Chrome Panel. I started from a fork of his project, but later started fresh as there was a lot of SAML specific code that this project doesn't use/need. The UI is nearly identical, but the code underneath has become fairly different at this point.
The SAML Chrome Panel was a huge help and ispiration! Thank you Milton and contributors to the SAML Chrome Panel project!

BIN
public/images/BNPforChromeScreenshot.png View File

Before After
Width: 1896  |  Height: 952  |  Size: 115 KiB

BIN
public/images/BNPforChromeScreenshotOriginal.png View File

Before After
Width: 1896  |  Height: 952  |  Size: 115 KiB

BIN
public/images/bnpscreenshot.png View File

Before After
Width: 1509  |  Height: 423  |  Size: 60 KiB

+ 2
- 2
views/pages/about.html View File

@ -8,8 +8,8 @@
<!-- <link rel="stylesheet" href="core.css"> -->
<style>
body {
background-color: #222 !important;
color: #fff !important;
background-color: #ddd !important;
color: #222 !important;
font-family: monospace !important;
font-size: 20px !important;
line-height: 1.4 !important;

+ 2
- 5
views/pages/index.ejs View File

@ -30,11 +30,8 @@
are good for the body and help build strong muscles. Skim is good, 1% is good, 2% is even better.
</p>
<p>He thought for a while, and said...<br /><br /></p>
<blockquote>
<p>Dad, have you been drinking 100% milk?
<br />Cause your muscles are sooo big!
</p>
</blockquote>
<blockquote><p>"Dad, have you been drinking 100% milk?
<br /> Cause your muscles are sooo big!"</p></blockquote>
<p>And that my friends is why being a dad is the best.</p>
</div>
<footer class="entry-footer-container">

Loading…
Cancel
Save