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
- func Deadline(w http.ResponseWriter, d time.Duration)
- func ErrMissingPart(name string) error
- func ErrUnknownPart(name string) error
- func IsMultipart(r *http.Request) bool
- func OnePart(r *http.Request, name string) (files.Upload, error)
- func Serve(w http.ResponseWriter, r *http.Request, c *files.Content, inlineTypes []string)
- type Form
- type Part
Constants ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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.