filehttp

package
v0.6.7 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package filehttp is the HTTP shape of an upload and a download.

It is here rather than in generated code because it is four hundred lines that would otherwise be regenerated identically into every project — and because every one of the decisions in it has an obvious wrong answer that a generator golden would never notice. The type a download announces, whether a form spills to disk, whether a cap truncates or refuses: each is one line, and each is the difference between a feature and an incident.

The generated handler calls ReadForm on the way in and Serve on the way out. Nothing here decides who may do anything; that has already happened by the time a call gets in.

Index

Constants

View Source
const DefaultDeadline = 30 * time.Minute

DefaultDeadline is how long one transfer gets. Generous, because it is bounded by the size cap on the way in and by the client on the way out, and a slow mobile connection uploading twenty megabytes is not a failure.

View Source
const JSONPart = "json"

JSONPart is the part a multipart create carries its row in.

One fixed name rather than one per resource: the form already names its file parts after the columns they land on, and a second naming scheme for the body would be a thing to look up for no gain.

Variables

This section is empty.

Functions

func Deadline

func Deadline(w http.ResponseWriter, d time.Duration)

Deadline extends this one request's clock without touching the server's.

ReadTimeout and WriteTimeout are set once on the one http.Server, so raising them for a two-hundred-megabyte upload weakens every other route in the application. This is the per-request form, which is why there is no UploadTimeout field on serve.Config and should not be one.

A server that does not support the control simply keeps its own deadlines, which is the right failure: the upload is bounded by something rather than by nothing.

func ErrMissingPart

func ErrMissingPart(name string) error

ErrMissingPart is what a form missing a required file part produces.

A field error and not a 500: a not-null file column with no part is the same class of mistake as a missing required field, and it should read like one.

func ErrUnknownPart

func ErrUnknownPart(name string) error

ErrUnknownPart is what a form carrying a part nobody claimed produces.

Refused rather than skipped, for the reason DisallowUnknownFields refuses an unknown key: a client that misspelled a part name has uploaded a file into nowhere and would otherwise get a 201 saying it worked.

func IsMultipart

func IsMultipart(r *http.Request) bool

IsMultipart reports whether a request carries a form.

It is how a create decides which of its two bodies it was sent. A request with no multipart content type takes the JSON path it has always taken, byte for byte.

func OnePart

func OnePart(r *http.Request, name string) (files.Upload, error)

OnePart reads a form that carries exactly one file, under the given name.

It is the upload route's whole body handling. The returned upload's Body is the part itself and is streamed, so the caller has to consume it before doing anything else with the request.

A second part, or a first one under another name, is refused rather than ignored — see ErrUnknownPart.

func Serve

func Serve(w http.ResponseWriter, r *http.Request, c *files.Content, inlineTypes []string)

Serve writes a file's bytes back.

http.ServeContent does the work, which is what gets range and conditional requests answered for free — a resumed download that does not start over, and a media element that can seek. Both are the kind of thing discovered in production rather than in a test, and neither is worth reimplementing.

The caller closes c.Body.

Types

type Form

type Form struct {

	// JSON is the "json" part's bytes, when the form had one. It is read eagerly
	// because it is small by construction and because a create needs it before
	// it can decide what to do with the files.
	JSON []byte
	// contains filtered or unexported fields
}

Form is a multipart request, read one part at a time.

The parts arrive in the order the client wrote them and are not buffered, so the caller has to consume each one before asking for the next. That is the whole reason this is an iterator rather than a map: a map of parts is a map of files in memory.

func ReadForm

func ReadForm(r *http.Request) (*Form, error)

ReadForm starts reading a multipart body.

It uses r.MultipartReader rather than r.ParseMultipartForm, and that is not a style preference: ParseMultipartForm spills every part past its memory budget into os.TempDir, so an upload of a file larger than memory lands on the ephemeral disk of a container that probably does not have room for it — twice, once there and once in the store.

maxBytes caps each file part. It is applied by the reader the caller streams from, not here, because the cap has to refuse rather than truncate and only the thing doing the reading can tell the difference.

func (*Form) Next

func (f *Form) Next() (*Part, error)

Next reads the next part, or reports io.EOF.

The returned part's body is only valid until the following call: this is a stream, and the bytes are gone once the reader has moved on.

type Part

type Part struct {
	// Name is the part's name on the wire.
	Name string
	// Filename is what the client called the file, or empty for a part that is
	// not a file.
	Filename string
	// ContentType is what the client claimed. It is recorded and then largely
	// ignored: the type that is stored and served is the one sniffed from the
	// bytes.
	ContentType string
	// Body is the part's content, valid until Next is called again.
	Body io.Reader
}

Part is one part of a form: either the JSON body or a file.

func (*Part) Upload

func (p *Part) Upload() files.Upload

Upload turns a part into what files.Service takes.

Jump to

Keyboard shortcuts

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