Add strings.Builder vs bytes.Buffer; different options for line reading; http chunking;
This commit is contained in:
parent
51a73f98b2
commit
daa4051650
6 changed files with 245 additions and 5 deletions
20
lectures/09-io/httpchunking/solution1/main.go
Normal file
20
lectures/09-io/httpchunking/solution1/main.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func transfer(clientWriter io.Writer, responseBody io.Reader) {
|
||||||
|
_, _ = io.Copy(clientWriter, responseBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTrailers(r io.Reader) {
|
||||||
|
_, _ = io.Copy(os.Stdout, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
data := "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\nDate: Sun, 06 Nov 1994 08:49:37 GMT\r\nContent-MD5: 1B2M2Y8AsgTpgAmY7PhCfg==\r\n\r\n"
|
||||||
|
transfer(os.Stdout, strings.NewReader(data))
|
||||||
|
}
|
25
lectures/09-io/httpchunking/solution2/main.go
Normal file
25
lectures/09-io/httpchunking/solution2/main.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http/httputil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func transfer(clientWriter io.Writer, responseBody io.Reader) {
|
||||||
|
_, _ = io.Copy(
|
||||||
|
clientWriter,
|
||||||
|
httputil.NewChunkedReader(responseBody),
|
||||||
|
)
|
||||||
|
parseTrailers(responseBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTrailers(r io.Reader) {
|
||||||
|
_, _ = io.Copy(os.Stdout, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
data := "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\nDate: Sun, 06 Nov 1994 08:49:37 GMT\r\nContent-MD5: 1B2M2Y8AsgTpgAmY7PhCfg==\r\n\r\n"
|
||||||
|
transfer(os.Stdout, strings.NewReader(data))
|
||||||
|
}
|
31
lectures/09-io/httpchunking/solution3/main.go
Normal file
31
lectures/09-io/httpchunking/solution3/main.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"io"
|
||||||
|
"net/http/httputil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func transfer(clientWriter io.Writer, responseBody io.Reader) {
|
||||||
|
digest := md5.New()
|
||||||
|
_, _ = io.Copy(digest,
|
||||||
|
httputil.NewChunkedReader(
|
||||||
|
io.TeeReader(
|
||||||
|
responseBody,
|
||||||
|
clientWriter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
parseTrailers(responseBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTrailers(r io.Reader) {
|
||||||
|
_, _ = io.Copy(os.Stdout, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
data := "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\nDate: Sun, 06 Nov 1994 08:49:37 GMT\r\nContent-MD5: 1B2M2Y8AsgTpgAmY7PhCfg==\r\n\r\n"
|
||||||
|
transfer(os.Stdout, strings.NewReader(data))
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
io
|
io
|
||||||
Лекция 9
|
Лекция 9
|
||||||
|
|
||||||
Someone Someone
|
Арсений Балобанов
|
||||||
|
|
||||||
* io.Reader & io.Writer
|
* io.Reader & io.Writer
|
||||||
|
|
||||||
|
@ -76,6 +76,52 @@ Someone Someone
|
||||||
|
|
||||||
.play -edit teereader/main.go /^func main/,/^}/
|
.play -edit teereader/main.go /^func main/,/^}/
|
||||||
|
|
||||||
|
* Example: http chunking
|
||||||
|
|
||||||
|
Chunked transfer encoding is a streaming data transfer mechanism.
|
||||||
|
|
||||||
|
4\r\n
|
||||||
|
Wiki\r\n
|
||||||
|
5\r\n
|
||||||
|
pedia\r\n
|
||||||
|
E\r\n
|
||||||
|
in\r\n
|
||||||
|
\r\n
|
||||||
|
chunks.\r\n
|
||||||
|
0\r\n
|
||||||
|
Date: Sun, 06 Nov 1994 08:49:37 GMT\r\n
|
||||||
|
Content-MD5: 1B2M2Y8AsgTpgAmY7PhCfg==\r\n
|
||||||
|
\r\n
|
||||||
|
|
||||||
|
Actual body
|
||||||
|
|
||||||
|
Wikipedia in
|
||||||
|
|
||||||
|
chunks.
|
||||||
|
|
||||||
|
* Example: http chunking
|
||||||
|
|
||||||
|
* Example: http chunking
|
||||||
|
|
||||||
|
Problem: proxy a chunked HTTP in a stream.
|
||||||
|
|
||||||
|
func transfer(clientWriter io.Writer, responseBody io.Reader)
|
||||||
|
|
||||||
|
- send chunks as is
|
||||||
|
- validate MD5
|
||||||
|
|
||||||
|
* Example: http chunking
|
||||||
|
|
||||||
|
.play -edit httpchunking/solution1/main.go
|
||||||
|
|
||||||
|
* Example: http chunking
|
||||||
|
|
||||||
|
.play -edit httpchunking/solution2/main.go /^func transfer/,/^}/
|
||||||
|
|
||||||
|
* Example: http chunking
|
||||||
|
|
||||||
|
.play -edit httpchunking/solution3/main.go /^func transfer/,/^}/
|
||||||
|
|
||||||
* ioutil
|
* ioutil
|
||||||
|
|
||||||
Package *io/ioutil* implements some I/O utility functions.
|
Package *io/ioutil* implements some I/O utility functions.
|
||||||
|
@ -111,7 +157,7 @@ Consider *io.Copy* instead
|
||||||
|
|
||||||
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)
|
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)
|
||||||
|
|
||||||
- Reuse your own buffer by with CopyBuffer()
|
- Reuse your own buffer with CopyBuffer()
|
||||||
|
|
||||||
Can we avoid using an intermediate buffer entirely?
|
Can we avoid using an intermediate buffer entirely?
|
||||||
|
|
||||||
|
@ -125,6 +171,8 @@ Can we avoid using an intermediate buffer entirely?
|
||||||
|
|
||||||
* Example: sendfile
|
* Example: sendfile
|
||||||
|
|
||||||
|
* Example: sendfile
|
||||||
|
|
||||||
.code -edit sendfile/main.go /^func readFileHandler/,/^}/
|
.code -edit sendfile/main.go /^func readFileHandler/,/^}/
|
||||||
|
|
||||||
* Example: sendfile
|
* Example: sendfile
|
||||||
|
@ -154,7 +202,7 @@ Can we avoid using an intermediate buffer entirely?
|
||||||
|
|
||||||
* Example: sendfile
|
* Example: sendfile
|
||||||
|
|
||||||
- http.ResponseWriter's is a io.ReaderFrom that uses the implementation of underlying tcp conn.
|
- http.ResponseWriter's is an io.ReaderFrom that uses the implementation of underlying tcp conn.
|
||||||
|
|
||||||
// ReadFrom is here to optimize copying from an *os.File regular file
|
// ReadFrom is here to optimize copying from an *os.File regular file
|
||||||
// to a *net.TCPConn with sendfile.
|
// to a *net.TCPConn with sendfile.
|
||||||
|
@ -162,7 +210,6 @@ Can we avoid using an intermediate buffer entirely?
|
||||||
|
|
||||||
- net.TCPConn also implements io.ReaderFrom that uses *sendfile* system call.
|
- net.TCPConn also implements io.ReaderFrom that uses *sendfile* system call.
|
||||||
|
|
||||||
// ReadFrom implements the io.ReaderFrom ReadFrom method.
|
|
||||||
func (c *TCPConn) ReadFrom(r io.Reader) (int64, error) {
|
func (c *TCPConn) ReadFrom(r io.Reader) (int64, error) {
|
||||||
|
|
||||||
* ioutil.Discard
|
* ioutil.Discard
|
||||||
|
@ -277,6 +324,23 @@ Utility type to efficiently read independent lines of text from an io.Reader.
|
||||||
|
|
||||||
.code -edit scanner/main.go
|
.code -edit scanner/main.go
|
||||||
|
|
||||||
|
* bufio read line
|
||||||
|
|
||||||
|
There are multiple options to read single line. Which one to use
|
||||||
|
*ReadBytes('\n')* or *ReadString('\n')* or *ReadLine* or *Scanner*?
|
||||||
|
|
||||||
|
- *ReadBytes* returns the slice together with delimiter
|
||||||
|
- *ReadLine* doesn’t handle lines longer than internal buffer (default size 4096)
|
||||||
|
- *Scanner* has limited max size of the token (64 * 1024)
|
||||||
|
- *ReadLine* needs to be called for the second time to retrieve rest of the stream
|
||||||
|
- *ReadBytes* doesn’t have any limit
|
||||||
|
- *Scanner* has the simplest API and provides nicest abstraction for common cases
|
||||||
|
|
||||||
|
* bufio
|
||||||
|
|
||||||
|
- The net/http package already buffers data (using bufio itself) so you don't need this package for that
|
||||||
|
- If you are reading a file in one or a few large steps, you probably don't need buffering
|
||||||
|
|
||||||
* bytes.Buffer
|
* bytes.Buffer
|
||||||
|
|
||||||
A handy wrapper around byte slice implementing `io.Reader` and `io.Writer`.
|
A handy wrapper around byte slice implementing `io.Reader` and `io.Writer`.
|
||||||
|
@ -285,14 +349,97 @@ Useful when you want to use code that takes an io.Writer, and store the results
|
||||||
|
|
||||||
.play -edit bytesbuffer/main.go
|
.play -edit bytesbuffer/main.go
|
||||||
|
|
||||||
|
* bytes.Buffer vs strings.Builder
|
||||||
|
|
||||||
|
bytes.Buffer
|
||||||
|
func NewBufferString(s string) *Buffer
|
||||||
|
func (b *Buffer) Bytes() []byte
|
||||||
|
func (b *Buffer) Grow(n int)
|
||||||
|
func (b *Buffer) Read(p []byte) (n int, err error)
|
||||||
|
func (b *Buffer) ReadByte() (byte, error)
|
||||||
|
// other read methods
|
||||||
|
func (b *Buffer) Reset()
|
||||||
|
func (b *Buffer) String() string
|
||||||
|
func (b *Buffer) Write(p []byte) (n int, err error)
|
||||||
|
func (b *Buffer) WriteByte(c byte) error
|
||||||
|
func (b *Buffer) WriteRune(r rune) (n int, err error)
|
||||||
|
func (b *Buffer) WriteString(s string) (n int, err error)
|
||||||
|
|
||||||
|
strings.Builder
|
||||||
|
func (b *Builder) Grow(n int)
|
||||||
|
func (b *Builder) Reset()
|
||||||
|
func (b *Builder) String() string
|
||||||
|
func (b *Builder) Write(p []byte) (int, error)
|
||||||
|
func (b *Builder) WriteByte(c byte) error
|
||||||
|
func (b *Builder) WriteRune(r rune) (int, error)
|
||||||
|
func (b *Builder) WriteString(s string) (int, error)
|
||||||
|
|
||||||
|
* bytes.Buffer vs strings.Builder
|
||||||
|
|
||||||
|
- *strings.Builder* is immutable and can only grow or reset
|
||||||
|
- *bytes.Buffer*'s internal byte slice can escape: (*Buffer).Bytes().
|
||||||
|
- strings.Builder.String() does not allocate/copy
|
||||||
|
|
||||||
|
// String returns the accumulated string.
|
||||||
|
func (b *Builder) String() string {
|
||||||
|
return *(*string)(unsafe.Pointer(&b.buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
- bytes.Buffer.String() does
|
||||||
|
|
||||||
|
func (b *Buffer) String() string {
|
||||||
|
if b == nil {
|
||||||
|
// Special case, useful in debugging.
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return string(b.buf[b.off:])
|
||||||
|
}
|
||||||
|
|
||||||
|
* strings.Builder vs strings.Builder
|
||||||
|
|
||||||
|
.play -edit stringsbuilder/main.go
|
||||||
|
|
||||||
|
* bytes.Buffer vs strings.Builder
|
||||||
|
|
||||||
|
- *strings.Builder* has a copy check
|
||||||
|
|
||||||
|
var b1 strings.Builder
|
||||||
|
b1.WriteString("ABC")
|
||||||
|
b2 := b1
|
||||||
|
b2.WriteString("DEF")
|
||||||
|
// illegal use of non-zero Builder copied by value
|
||||||
|
|
||||||
|
- Use pointer to share.
|
||||||
|
|
||||||
* *os.File
|
* *os.File
|
||||||
|
|
||||||
- portable
|
- portable
|
||||||
- implements io.Reader, and io.Writer which stream bytes to or from a file on disk
|
- implements io.Reader, and io.Writer which stream bytes to or from a file on disk
|
||||||
- useful if you don't want to read the whole file into memory
|
- useful if you don't want to read the whole file into memory
|
||||||
|
- has no internal buffers
|
||||||
|
|
||||||
*ioutil.ReadFile* reads an entire file into memory (as a []byte) in a single call
|
*ioutil.ReadFile* reads an entire file into memory (as a []byte) in a single call
|
||||||
|
|
||||||
- allocates a byte slice of the correct size (no need to Read + append in a loop)
|
- allocates a byte slice of the correct size (no need to Read + append in a loop)
|
||||||
- closes the file
|
- closes the file
|
||||||
- returns the first error that prevented it from working
|
- returns the first error that prevented it from working
|
||||||
|
|
||||||
|
There are also
|
||||||
|
|
||||||
|
func WriteFile(filename string, data []byte, perm os.FileMode) error
|
||||||
|
func TempFile(dir, pattern string) (f *os.File, err error)
|
||||||
|
|
||||||
|
* Summary
|
||||||
|
|
||||||
|
- *io* defines interfaces that handle streams of bytes (Reader, Writer, etc...) as well as functions that work generically with types implement these interfaces (e.g. io.Copy)
|
||||||
|
- *io/ioutil* provides helper functions for some non-trivial file and io tasks
|
||||||
|
- *testing/iotest* implements Readers and Writers useful mainly for testing
|
||||||
|
- *bufio* provides buffering wrapper for io.Reader and io.Writer that can improve efficiency
|
||||||
|
- *bytes* provides helper functions and types for interacting with byte slices
|
||||||
|
- ***os.File* implements both io.Reader and io.Writer (among others)
|
||||||
|
|
||||||
|
* Links
|
||||||
|
|
||||||
|
.link https://medium.com/go-walkthrough/go-walkthrough-io-package-8ac5e95a9fbd io walkthrough
|
||||||
|
.link https://medium.com/golangspec/introduction-to-bufio-package-in-golang-ad7d1877f762 bufio walkthrough
|
||||||
|
.link https://www.youtube.com/watch?v=kTAsciVuZLQ advanced patterns with io.ReadWriter
|
||||||
|
|
18
lectures/09-io/stringsbuilder/main.go
Normal file
18
lectures/09-io/stringsbuilder/main.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func concat(x, y string) string {
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.Grow(len(x) + len(y)) // only this line allocates
|
||||||
|
builder.WriteString(x)
|
||||||
|
builder.WriteString(y)
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println(concat("hello ", "world"))
|
||||||
|
}
|
|
@ -25,5 +25,4 @@ func main() {
|
||||||
|
|
||||||
printall(tee)
|
printall(tee)
|
||||||
printall(&buf)
|
printall(&buf)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue