惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
AWS News Blog
AWS News Blog
Last Week in AI
Last Week in AI
宝玉的分享
宝玉的分享
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
腾讯CDC
Apple Machine Learning Research
Apple Machine Learning Research
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学
爱范儿
爱范儿
IT之家
IT之家
博客园 - 叶小钗
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
SegmentFault 最新的问题
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
G
GRAHAM CLULEY
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
博客园 - 聂微东
有赞技术团队
有赞技术团队
J
Java Code Geeks
小众软件
小众软件
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
V2EX
The Last Watchdog
The Last Watchdog
S
Securelist
博客园 - Franky
罗磊的独立博客
阮一峰的网络日志
阮一峰的网络日志
AI
AI
雷峰网
雷峰网
博客园 - 司徒正美
L
LINUX DO - 热门话题
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏
Hugging Face - Blog
Hugging Face - Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
L
LINUX DO - 最新话题
TaoSecurity Blog
TaoSecurity Blog
Cisco Talos Blog
Cisco Talos Blog
K
Kaspersky official blog
H
Hacker News: Front Page
量子位
Latest news
Latest news

Ivan on Containers, Kubernetes, and Server-Side

A grounded take on agentic coding for production environments Server-Side Playgrounds Reimagined: Build, Boot, and Network Your Own Virtual Labs [not a] Kubernetes 101 - Pods, Deployments, and Services As an Attempt To Automate Age-Old Infra Patterns JavaScript or TypeScript? How To Benefit From the Dichotomy On Software Design... and Good Writing Building a Firecracker-Powered Course Platform To Learn Docker and Kubernetes How To Publish a Port of a Running Container What Actually Happens When You Publish a Container Port A Visual Guide to SSH Tunnels: Local and Remote Port Forwarding Debugging Containers Like a Pro Docker: How To Debug Distroless And Slim Containers How To Extract Container Image Filesystem Using Docker | iximiuz Labs In Pursuit of Better Container Images: Alpine, Distroless, Apko, Chisel, DockerSlim, oh my! How To Start Programming In Go: Advice For Fellow DevOps Engineers Kubernetes Ephemeral Containers and kubectl debug Command How To Develop Kubernetes CLIs Like a Pro Docker Container Commands Explained: Understand, Don't Memorize | iximiuz Labs Learning Docker with Docker - Toying With DinD For Fun And Profit How To Extend Kubernetes API - Kubernetes vs. Django How To Call Kubernetes API from Go - Types and Common Machinery How To Call Kubernetes API using Simple HTTP Client Kubernetes API Basics - Resources, Kinds, and Objects OpenFaaS - Run Containerized Functions On Your Own Terms Learning Containers From The Bottom Up Docker Containers vs. Kubernetes Pods - Taking a Deeper Look | iximiuz Labs Learn-by-Doing Platforms for Dev, DevOps, and SRE Folks How HTTP Keep-Alive can cause TCP race condition How to Work with Container Images Using ctr | iximiuz Labs Multiple Containers, Same Port, no Reverse Proxy... Exploring Go net/http Package - On How Not To Set Socket Options Disposable Local Development Environments with Vagrant, Docker, and Arkade DevOps, SRE, and Platform Engineering My Choice of Programming Languages Prometheus Is Not a TSDB How to learn PromQL with Prometheus Playground Prometheus Cheat Sheet - Basics (Metrics, Labels, Time Series, Scraping) Rust - Writing Parsers With nom Parser Combinator Framework pq - parse and query log files as time series Prometheus Cheat Sheet - Moving Average, Max, Min, etc (Aggregation Over Time) Prometheus Cheat Sheet - How to Join Multiple Metrics (Vector Matching) The Need For Slimmer Containers Understanding Rust Privacy and Visibility Model Bridge vs. Switch: Takeaways from a Real Data Center Tour | iximiuz Labs From LAN to VXLAN: Networking Basics for Non-Network Engineers | iximiuz Labs KiND - How I Wasted a Day Loading Local Docker Images Go, HTTP handlers, panic, and deadlocks Exploring Kubernetes Operator Pattern Making Sense Out Of Cloud Native Buzz Service Discovery in Kubernetes: Combining the Best of Two Worlds API Developers Never REST How Container Networking Works: Building a Bridge Network From Scratch | iximiuz Labs Traefik: canary deployments with weighted load balancing Service Proxy, Pod, Sidecar, oh my! You Need Containers To Build Images You Don't Need an Image To Run a Container Not Every Container Has an Operating System Inside Working with container images in Go Master Go While Learning Containers Implementing Container Runtime Shim: Interactive Containers How to use Flask with gevent (uWSGI and Gunicorn editions) My 10 Years of Programming Experience Implementing Container Runtime Shim: First Code Implementing Container Runtime Shim: runc Kubernetes Repository On Flame Dealing with process termination in Linux (with Rust examples) conman - [the] Container Manager: Inception Journey From Containerization To Orchestration And Beyond Linux PTY - How docker attach and docker exec Commands Work Inside Illustrated introduction to Linux iptables From Docker Container to Bootable Linux Disk Image Пишем свой веб-сервер на Python: протокол HTTP 9001 способ создать веб-сервер на Python Explaining async/await in 200 lines of code Explaining event loop in 100 lines of code Save the day with gevent Пишем свой веб-сервер на Python: процессы, потоки и асинхронный I/O Truly optional scalar types in protobuf3 (with Go examples) Node.js Writable streams distilled Node.js Readable streams distilled How to on starting processes (mostly in Linux) Дайджест интересных ссылок – Июль 2016 Пишем свой веб-сервер на Python: сокеты Наследование в JavaScript Мастерить!
The Influence of Plumbing on Programming
Ivan Velichko · 2022-02-20 · via Ivan on Containers, Kubernetes, and Server-Side

I strive to produce concise but readable code. One of my favorite tactics - minimizing the number of local variables - usually can be achieved through minting or discovering higher-level abstractions and joining them together in a more or less declarative way.

Thus, when writing Go code, I often utilize io.Reader and io.Writer interfaces and the related io machinery. A function like io.Copy(w io.Writer, r io.Reader) can be a perfect example of such a higher-level abstraction - it's a succinct at devtime and efficient at runtime way to move some bytes from the origin r to the destination w.

But conciseness often comes at a price, especially in the case of I/O handling - getting a sneak peek at the data that's being passed around becomes much trickier in the code that relies on the Reader and Writer abstractions.

So, is there an easy way to see the data that comes out of readers or goes into writers without a significant code restructure or an introduction of temporary variables?

Level up your server-side game — join 20,000 engineers getting insightful learning materials straight to their inbox.

Pipelines and T-splitters

When I stumbled upon this problem in Go for the first time, it immediately reminded me of another similar problem I'd come across years ago. As a Linux person, I often construct multi-command pipelines to process data right in the terminal:

  ... | grep | sed | sort | uniq | ...

>----->------>----->------>------>----->
[stdin]                         [stdout]

In the above pipeline, the stdout stream of every but the last command is piped into the stdin stream of the following command, and the output of the very last command is dumped onto the screen. In other words, the data flow in the pipeline is linear.

Continuing the plumbing analogies, if I needed to take a look at the data from an intermediate step of the pipeline while keeping the full pipeline running, I'd need to attach kinda sorta T-splitter. And as it usually happens, there is already a shell command for that called T [tee]!

  ... | grep | sed | tee tmp.log | sort | uniq | ...

>----->------>----->------┬------>------>------>----->
[stdin]                   |                   [stdout]
                          |
                          |
                          v
                       [tmp.log]

Much like a real T-splitter, the tee command can be injected at any position in a pipeline connecting the stdout of its preceding command with the stdin of the succeeding command and copying the passed through data to a file.

Since using T-splitters is a common pattern when dealing with I/O streams, it'd be reasonable to expect that the Go standard library should be providing similar capabilities. And indeed, a quick search for the tee word on the io package docs page resulted in a few matches! Turns out, Go's io package comes with tee-like functionality included. However, it's a bit more fine-grained than its shell ancestor.

io.TeeReader

In Go, attaching a T-splitter to an io.Reader is different from attaching a T-splitter to an io.Writer. Let's see how it can be done in both cases.

Go io.TeeReader illustrated.

Starting from the io.Reader case - there is a utility function io.TeeReader that wraps an existing reader into a thin auxiliary structure that copies everything read from the original reader to a specified writer:

// TeeReader returns a Reader that writes to w what it reads from r.
// ...
func TeeReader(r Reader, w Writer) Reader
Expand to see the code of TeeReader - it's actually pretty simple!

Taken from src/io/io.go:

type teeReader struct {
  r Reader
  w Writer
}

func TeeReader(r Reader, w Writer) Reader {
  return &teeReader{r, w}
}

func (t *teeReader) Read(p []byte) (n int, err error) {
  n, err = t.r.Read(p)
  if n > 0 {
    if n, err := t.w.Write(p[:n]); err != nil {
      return n, err
    }
  }
  return
}

io.MultiWriter

The writer's case is not really T-shaped, but still, it resembles some plumbing work. An arbitrary number of io.Writers can be joined into a single io.Writer instance using the io.MultiWriter utility function:

Go io.MultiWriter illustrated.

Notice that the Go documentation for the MultiWriter does mention the Unix tee utility:

// MultiWriter creates a writer that duplicates its writes to all the
// provided writers, similar to the Unix tee(1) command.
// ...
func MultiWriter(writers ...Writer) Writer
Expand to see the code of MultiWriter - it's a bit more verbose but still a piece of cake.

Taken from src/io/multi.go:

type multiWriter struct {
  writers []Writer
}

func MultiWriter(writers ...Writer) Writer {
  allWriters := make([]Writer, 0, len(writers))
  for _, w := range writers {
    if mw, ok := w.(*multiWriter); ok {
      allWriters = append(allWriters, mw.writers...)
    } else {
      allWriters = append(allWriters, w)
    }
  }
  return &multiWriter{allWriters}
}

func (t *multiWriter) Write(p []byte) (n int, err error) {
  for _, w := range t.writers {
    n, err = w.Write(p)
    if err != nil {
      return
    }
    if n != len(p) {
      err = ErrShortWrite
      return
    }
  }
  return len(p), nil
}

io.TeeReader and io.MultiWriter example

Here is an example of a real-world program I was debugging a couple of days ago using the tee-trick. Of course, I stripped it to the bones to keep the focus on the relevant parts:

package main

import (
  "encoding/json"
  "net/http"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    // The request structure is non-trivial.
    // E.g., has many optional and nested fields.
    request := make(map[string]interface{})
    json.NewDecoder(r.Body).Decode(&request)

    // The actual response creation logic is
    // hidden behind many layers of abstraction.
    response := map[string]string{
      "message": "Looks very good!"
    }
    json.NewEncoder(w).Encode(response)
  })

  http.ListenAndServe(":8888", nil)
}

My goal was to get the actual request and response bodies dumped to the stdout without any significant code reshaping. So, after applying the tee-trick, the code started looking as follows:

package main

import (
  "encoding/json"  
  "io"
  "net/http"
  "os"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    request := make(map[string]interface{})
  
    // 👉 io.TeeReader
    json.NewDecoder(io.TeeReader(r.Body, os.Stdout)).
      Decode(&request)
  
    response := map[string]string{
        "message": "Looks very good!"
    }
  
    // 👉 io.MultiWriter
    json.NewEncoder(io.MultiWriter(os.Stdout, w)).
      Encode(response)
  })

  http.ListenAndServe(":8888", nil)
}

Essentially, the change was a one-liner. Test it out with:

$ go run server.go &
$ curl -X POST -d '{"foo": "bar"}' localhost:8888

Instead of conclusion

One of the things I enjoyed the most while playing with this code was the elegance of the Go standard library. Adding tee-like functionality didn't bloat the io package with unnecessary type definitions - the teeReader and multiWriter are just hidden private structs, while the public API surface was merely extended with two plain functions that operate in terms of the basic io.Reader and io.Writer interfaces. Full reuse of the existing concepts!

However, the main takeaway here is probably unrelated to the blog post's subject - often, an ability to notice a conceptual pattern compensates for the lack of knowledge of the standard library of the language at hand.

Level up your server-side game — join 20,000 engineers getting insightful learning materials straight to their inbox: