formstream

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 6, 2024 License: MIT Imports: 10 Imported by: 0

README

FormStream

GitHub release CI main codecov Go Reference

FormStream is a Golang streaming parser for multipart data, primarily used in web form submissions and file uploads.

Features

  • Provides a streaming parser, eliminating the need to store entire files in memory or on disk in most cases.
  • Boasts extremely low memory usage.
  • Delivers high performance, significantly faster than traditional methods.

Benchmarks

Across all file sizes, FormStream outperforms the mime/multipart in both speed and memory efficiency.

Testing Environment
  • OS: Ubuntu 22.04.2 LTS(WSL2 on Windows 11 Home)
  • CPU: AMD Ryzen 9 7950X 16-Core Processor
  • RAM: 32GB
  • Disk: 512GB
  • Go version: 1.22.0

[!NOTE] FormStream excels in speed by employing a stream for parsing multipart data that meets specific conditions (as shown in the FastPath on the graph). It remains significantly efficient even under less ideal conditions (SlowPath on the graph), marginally outperforming mime/multipart. For more details, see Technical Overview.

Installation

go get github.com/mazrean/formstream@latest

Usage

Basic Usage
Example Data
--boundary
Content-Disposition: form-data; name="name"

mazrean
--boundary
Content-Disposition: form-data; name="password"

password
--boundary
Content-Disposition: form-data; name="icon"; filename="icon.png"
Content-Type: image/png

icon contents
--boundary--
parser, err := formstream.NewParser(r)
if err != nil {
    return err
}

err = parser.Register("icon", func(r io.Reader, header formstream.Header) error {
    name, _, _ := parser.Value("name")
    password, _, _ := parser.Value("password")

    return saveUser(r.Context(), name, password, r)
}, formstream.WithRequiredPart("name"), formstream.WithRequiredPart("password"))
if err != nil {
    return err
}

err = parser.Parse()
if err != nil {
    return err
}
Integration with Web Frameworks

FormStream offers wrappers for popular web frameworks:

Framework Integration Package
net/http httpform
Echo echoform
Gin ginform

Technical Overview

FormStream introduces a more efficient method for processing multipart data.

Understanding Multipart Data

Multipart data is organized with defined boundaries separating each segment. Here's an example:

--boundary
Content-Disposition: form-data; name="description"

file description
--boundary
Content-Disposition: form-data; name="file"; filename="large.png"
Content-Type: image/png

large png data...
--boundary--

For large files, streaming the data is vital for efficient memory usage. In the example above, streaming is made possible by sequentially processing each part from the beginning, which can be achieved using the (*Reader).NextPart method in the mime/multipart package.

Alternative Parsing Method

The mime/multipart package also includes the (*Reader).ReadForm method. Unlike streaming, this method stores data temporarily in memory or on a file, leading to slower processing. It's widely used in frameworks like net/http, Echo, and Gin due to its ability to handle parts in any order. For instance:

--boundary
Content-Disposition: form-data; name="file"; filename="large.png"
Content-Type: image/png

large png data...
--boundary
Content-Disposition: form-data; name="description"

file description
--boundary--

With (*Reader).NextPart, processing strictly follows sequential order, making it challenging to handle such data where later parts contain information necessary for processing earlier ones.

Efficient Processing Strategies

Optimal multipart handling strategies include:

  • Stream processing with (*Reader).NextPart when all necessary data is immediately available.
  • Temporarily storing data on disk or memory, then processing it with (*Reader).ReadForm when needed.
Advantages of FormStream

FormStream enhances this process. It outpaces the (*Reader).ReadForm method and, unlike (*Reader).NextPart, can handle multipart data in any order. This adaptability makes FormStream suitable for a range of multipart data scenarios.

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrTooManyParts is returned when the parts are more than MaxParts.
	ErrTooManyParts = errors.New("too many parts")
	// ErrTooManyHeaders is returned when the headers are more than MaxHeaders.
	ErrTooManyHeaders = errors.New("too many headers")
	// ErrTooLargeForm is returned when the form is too large for the parser to handle within the memory limit.
	ErrTooLargeForm = errors.New("too large form")
)

Functions

This section is empty.

Types

type DataSize

type DataSize int64
const (
	KB DataSize
	MB
	GB
)

type DuplicateHookNameError

type DuplicateHookNameError struct {
	Name string
}

func (DuplicateHookNameError) Error

func (e DuplicateHookNameError) Error() string
type Header struct {
	// contains filtered or unexported fields
}

func (Header) ContentType

func (h Header) ContentType() string

ContentType returns the value of the "Content-Type" header field. If there are no values associated with the key, ContentType returns "".

func (Header) FileName

func (h Header) FileName() string

FileName returns the value of the "filename" parameter in the "Content-Disposition" header field. If there are no values associated with the key, FileName returns "".

func (Header) Get

func (h Header) Get(key string) string

Get returns the first value associated with the given key. If there are no values associated with the key, Get returns "".

func (Header) Name

func (h Header) Name() string

Name returns the value of the "name" parameter in the "Content-Disposition" header field. If there are no values associated with the key, Name returns "".

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

func NewParser

func NewParser(boundary string, options ...ParserOption) *Parser
Example
package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"github.com/mazrean/formstream"
)

func main() {
	buf := strings.NewReader(`
--boundary
Content-Disposition: form-data; name="field"

value
--boundary
Content-Disposition: form-data; name="stream"; filename="file.txt"
Content-Type: text/plain

large file contents
--boundary--`)

	parser := formstream.NewParser("boundary")

	err := parser.Register("stream", func(r io.Reader, header formstream.Header) error {
		fmt.Println("---stream---")
		fmt.Printf("file name: %s\n", header.FileName())
		fmt.Printf("Content-Type: %s\n", header.ContentType())
		fmt.Println()

		_, err := io.Copy(os.Stdout, r)
		if err != nil {
			return fmt.Errorf("failed to copy: %w", err)
		}

		return nil
	})
	if err != nil {
		log.Fatal(err)
	}

	err = parser.Parse(buf)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("\n\n")
	fmt.Println("---field---")
	content, _, _ := parser.Value("field")
	fmt.Println(content)

}
Output:

---stream---
file name: file.txt
Content-Type: text/plain

large file contents

---field---
value

func (*Parser) Parse

func (p *Parser) Parse(r io.Reader) (err error)

Parse parses the multipart form from r.

func (*Parser) Register

func (p *Parser) Register(name string, fn StreamHookFunc, options ...RegisterOption) error

Register registers a stream hook with the given name.

func (*Parser) Value

func (p *Parser) Value(key string) (string, Header, bool)

Value first value of the key.

func (*Parser) ValueMap

func (p *Parser) ValueMap() map[string][]Value

ValueMap all values.

func (*Parser) ValueRaw

func (p *Parser) ValueRaw(key string) ([]byte, Header, bool)

ValueRaw first value of the key.

func (*Parser) Values

func (p *Parser) Values(key string) ([]Value, bool)

Values all values of the key.

type ParserOption

type ParserOption func(*parserConfig)

func WithMaxHeaders

func WithMaxHeaders(maxHeaders uint) ParserOption

WithMaxHeaders sets the maximum number of headers to be parsed. default: 10000

func WithMaxMemFileSize

func WithMaxMemFileSize(maxMemFileSize DataSize) ParserOption

WithMaxMemFileSize sets the maximum memory size to be used for parsing a file. default: 32MB

func WithMaxMemSize

func WithMaxMemSize(maxMemSize DataSize) ParserOption

WithMaxMemSize sets the maximum memory size to be used for parsing. default: 32MB

func WithMaxParts

func WithMaxParts(maxParts uint) ParserOption

WithMaxParts sets the maximum number of parts to be parsed. default: 10000

type RegisterOption

type RegisterOption func(*registerConfig)

func WithRequiredPart

func WithRequiredPart(name string) RegisterOption

WithRequiredPart sets the required part names for the stream hook.

type StreamHookFunc

type StreamHookFunc = func(r io.Reader, header Header) error

type Value

type Value struct {
	// contains filtered or unexported fields
}

func (Value) Unwrap

func (v Value) Unwrap() (string, Header)

Unwrap returns the content and header of the value.

func (Value) UnwrapRaw

func (v Value) UnwrapRaw() ([]byte, Header)

UnwrapRaw returns the raw content and header of the value.

Directories

Path Synopsis
internal
condition_judge/mock
Package mock is a generated GoMock package.
Package mock is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL