mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-06-17 12:28:57 -04:00
* add new configuration setting GROUPWARE_SEND_DURATIONS_RESPONSE (defaults to false) * keep track of lists of durations of backend calls * when enabled, report them as response headers Durations (human readable) and Durations-Nanos (as raw nanosecond values for machine consumption)
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
package groupware
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
|
"github.com/opencloud-eu/opencloud/pkg/log"
|
|
)
|
|
|
|
const (
|
|
DefaultBlobDownloadType = "application/octet-stream"
|
|
)
|
|
|
|
func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
|
|
get(Blob, w, r, g, g.jmap.GetBlobMetadata)
|
|
}
|
|
|
|
func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
|
|
g.respond(w, r, func(req Request) Response {
|
|
contentType := r.Header.Get("Content-Type")
|
|
body := r.Body
|
|
if body != nil {
|
|
defer func(Body io.ReadCloser) {
|
|
err := Body.Close()
|
|
if err != nil {
|
|
req.logger.Error().Err(err).Msg("failed to close response body")
|
|
}
|
|
}(body)
|
|
}
|
|
|
|
accountId, err := req.GetAccountIdForBlob()
|
|
if err != nil {
|
|
return req.errorV(accountId, err)
|
|
}
|
|
logger := log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)))
|
|
ctx := req.ctx.WithLogger(logger)
|
|
before := time.Now()
|
|
resp, _, jerr := g.jmap.UploadBlobStream(accountId, contentType, body, ctx)
|
|
duration := time.Since(before)
|
|
if jerr != nil {
|
|
return req.jmapError(accountId, jerr, req.session)
|
|
}
|
|
|
|
return req.respondWithoutStatus(accountId, resp, single(duration))
|
|
})
|
|
}
|
|
|
|
// Download a BLOB by its identifier.
|
|
func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) {
|
|
g.stream(w, r, func(req Request, w http.ResponseWriter) (bool, Response) {
|
|
ok, accountId, resp := req.needBloblWithAccount()
|
|
if !ok {
|
|
return false, resp
|
|
}
|
|
|
|
blobId, err := req.PathParam(UriParamBlobId) // the unique identifier of the blob to download
|
|
if err != nil {
|
|
return false, req.errorV(accountId, err)
|
|
}
|
|
name, err := req.PathParam(UriParamBlobName) // the filename of the blob to download, which is then used in the response and may be arbitrary if unknown
|
|
if err != nil {
|
|
return false, req.errorV(accountId, err)
|
|
}
|
|
typ, _ := req.getStringParam(QueryParamBlobType, "") // optionally, the Content-Type of the blob, which is then used in the response
|
|
|
|
logger := log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)).Str(UriParamBlobId, blobId))
|
|
ctx := req.ctx.WithLogger(logger)
|
|
|
|
before := time.Now()
|
|
if err := req.serveBlob(blobId, name, typ, ctx, accountId, w); err != nil {
|
|
return false, req.error(accountId, err, single(time.Since(before)))
|
|
} else {
|
|
return true, req.noop(accountId, single(time.Since(before)))
|
|
}
|
|
})
|
|
}
|
|
|
|
func (r *Request) serveBlob(blobId string, name string, typ string, ctx jmap.Context, accountId jmap.AccountId, w http.ResponseWriter) *Error { //NOSONAR
|
|
if typ == "" {
|
|
typ = DefaultBlobDownloadType
|
|
}
|
|
blob, lang, jerr := r.g.jmap.DownloadBlobStream(accountId, blobId, name, typ, ctx)
|
|
if blob != nil && blob.Body != nil {
|
|
defer func(Body io.ReadCloser) {
|
|
err := Body.Close()
|
|
if err != nil {
|
|
ctx.Logger.Error().Err(err).Msg("failed to close response body")
|
|
}
|
|
}(blob.Body)
|
|
}
|
|
if jerr != nil {
|
|
switch e := jerr.(type) {
|
|
case jmap.Error:
|
|
return r.apiErrorFromJmap(e)
|
|
default:
|
|
return apiError(r.errorId(), ErrorGeneric, withDetail(e.Error()))
|
|
}
|
|
}
|
|
if blob == nil {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return nil
|
|
}
|
|
|
|
if blob.Type != "" {
|
|
w.Header().Add("Content-Type", blob.Type)
|
|
}
|
|
if blob.CacheControl != "" {
|
|
w.Header().Add("Cache-Control", blob.CacheControl)
|
|
}
|
|
if blob.ContentDisposition != "" {
|
|
w.Header().Add("Content-Disposition", blob.ContentDisposition)
|
|
}
|
|
if blob.Size >= 0 {
|
|
w.Header().Add("Content-Size", strconv.Itoa(blob.Size))
|
|
}
|
|
if lang != "" {
|
|
w.Header().Add("Content-Language", string(lang))
|
|
}
|
|
|
|
_, err := io.Copy(w, blob.Body)
|
|
if err != nil {
|
|
return r.observedParameterError(ErrorStreamingResponse)
|
|
}
|
|
|
|
return nil
|
|
}
|