REST API: add location header to 201 responses

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2022-12-23 13:08:04 +01:00
parent ed949604d3
commit e5a8220b8a
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
8 changed files with 63 additions and 36 deletions

View file

@ -102,34 +102,11 @@ docker run --name some-sftpgo \
-d "drakkan/sftpgo:tag"
```
Setting the SFTPGO_GRACE_TIME environment variable to a non zero value when creating or running a container will enable a graceful shutdown period in seconds that will allow existing connections to hopefully complete before being forcibly closed when the time has passed.
Setting the `SFTPGO_GRACE_TIME` environment variable to a non zero value when creating or running a container will enable a graceful shutdown period in seconds that will allow existing connections to hopefully complete before being forcibly closed when the time has passed.
```shell
echo "put 10G.dd" | sftp -P 2022 testuser@sftpgo.example.net
Connected to sftpgo.example.net.
sftp> put 10G.dd
Uploading 10G.dd to /10G.dd
10G.dd 17% 1758MB 100.9MB/s 01:24 ETA
client_loop: send disconnect: Broken pipe
Connection closed.
```
While the SFTPGO container is in graceful shutdown mode waiting for the last connection(s) to finish, no new connections will be allowed.
```shell
Fri 23 Dec 2022 08:47:41 AM UTC
Connected to sftpgo.example.net.
sftp> put d.txt
Uploading d.txt to /d.txt
d.txt 100% 323 216.9KB/s 00:00
Fri 23 Dec 2022 08:47:42 AM UTC
kex_exchange_identification: Connection closed by remote host
Connection closed.
```
If no connections are active or SFTPGO_GRACE_TIME=0 the container will shutdown immediately.
While the SFTPGo container is in graceful shutdown mode waiting for the last connection(s) to finish, no new connections will be allowed.
If no connections are active or `SFTPGO_GRACE_TIME=0` (default value if unset) the container will shutdown immediately.
### Where to Store Data

View file

@ -17,6 +17,7 @@ package httpd
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/go-chi/jwtauth/v5"
@ -65,12 +66,13 @@ func renderAdmin(w http.ResponseWriter, r *http.Request, username string, status
func addAdmin(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" {
sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
return
}
var admin dataprovider.Admin
admin := dataprovider.Admin{}
err = render.DecodeJSON(r.Body, &admin)
if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
@ -81,6 +83,7 @@ func addAdmin(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
w.Header().Add("Location", fmt.Sprintf("%s/%s", adminPath, admin.Username))
renderAdmin(w, r, admin.Username, http.StatusCreated)
}

View file

@ -16,6 +16,7 @@ package httpd
import (
"context"
"fmt"
"net/http"
"github.com/go-chi/render"
@ -74,11 +75,13 @@ func addEventAction(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
err = dataprovider.AddEventAction(&action, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
err = dataprovider.AddEventAction(&action, claims.Username, ipAddr, claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
w.Header().Add("Location", fmt.Sprintf("%s/%s", eventActionsPath, action.Name))
renderEventAction(w, r, action.Name, http.StatusCreated)
}
@ -188,11 +191,12 @@ func addEventRule(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
err = dataprovider.AddEventRule(&rule, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
if err := dataprovider.AddEventRule(&rule, claims.Username, ipAddr, claims.Role); err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
w.Header().Add("Location", fmt.Sprintf("%s/%s", eventRulesPath, rule.Name))
renderEventRule(w, r, rule.Name, http.StatusCreated)
}

View file

@ -16,6 +16,7 @@ package httpd
import (
"context"
"fmt"
"net/http"
"github.com/go-chi/render"
@ -42,6 +43,7 @@ func getFolders(w http.ResponseWriter, r *http.Request) {
func addFolder(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" {
sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
@ -54,11 +56,11 @@ func addFolder(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return
}
err = dataprovider.AddFolder(&folder, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
if err := dataprovider.AddFolder(&folder, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role); err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
w.Header().Add("Location", fmt.Sprintf("%s/%s", folderPath, folder.Name))
renderFolder(w, r, folder.Name, http.StatusCreated)
}

View file

@ -16,6 +16,7 @@ package httpd
import (
"context"
"fmt"
"net/http"
"github.com/go-chi/render"
@ -58,6 +59,7 @@ func addGroup(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
w.Header().Add("Location", fmt.Sprintf("%s/%s", groupPath, group.Name))
renderGroup(w, r, group.Name, http.StatusCreated)
}

View file

@ -16,6 +16,7 @@ package httpd
import (
"context"
"fmt"
"net/http"
"github.com/go-chi/render"
@ -47,6 +48,7 @@ func addRole(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
return
}
var role dataprovider.Role
err = render.DecodeJSON(r.Body, &role)
if err != nil {
@ -56,9 +58,10 @@ func addRole(w http.ResponseWriter, r *http.Request) {
err = dataprovider.AddRole(&role, claims.Username, util.GetIPFromRemoteAddress(r.RemoteAddr), claims.Role)
if err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
} else {
w.Header().Add("Location", fmt.Sprintf("%s/%s", rolesPath, role.Name))
renderRole(w, r, role.Name, http.StatusCreated)
}
renderRole(w, r, role.Name, http.StatusCreated)
}
func updateRole(w http.ResponseWriter, r *http.Request) {

View file

@ -113,6 +113,7 @@ func addUser(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err))
return
}
w.Header().Add("Location", fmt.Sprintf("%s/%s", userPath, user.Username))
renderUser(w, r, user.Username, claims.Role, http.StatusCreated)
}

View file

@ -1385,6 +1385,11 @@ paths:
responses:
'201':
description: successful operation
headers:
Location:
schema:
type: string
description: 'URI of the newly created object'
content:
application/json; charset=utf-8:
schema:
@ -1560,6 +1565,11 @@ paths:
responses:
'201':
description: successful operation
headers:
Location:
schema:
type: string
description: 'URI of the newly created object'
content:
application/json; charset=utf-8:
schema:
@ -1735,6 +1745,11 @@ paths:
responses:
'201':
description: successful operation
headers:
Location:
schema:
type: string
description: 'URI of the newly created object'
content:
application/json; charset=utf-8:
schema:
@ -1910,6 +1925,11 @@ paths:
responses:
'201':
description: successful operation
headers:
Location:
schema:
type: string
description: 'URI of the newly created object'
content:
application/json; charset=utf-8:
schema:
@ -2085,6 +2105,11 @@ paths:
responses:
'201':
description: successful operation
headers:
Location:
schema:
type: string
description: 'URI of the newly created object'
content:
application/json; charset=utf-8:
schema:
@ -2574,7 +2599,7 @@ paths:
Location:
schema:
type: string
description: URL to retrieve the details for the new created API key
description: URI to retrieve the details for the new created API key
content:
application/json; charset=utf-8:
schema:
@ -2763,6 +2788,11 @@ paths:
responses:
'201':
description: successful operation
headers:
Location:
schema:
type: string
description: 'URI of the newly created object'
content:
application/json; charset=utf-8:
schema:
@ -3052,6 +3082,11 @@ paths:
responses:
'201':
description: successful operation
headers:
Location:
schema:
type: string
description: 'URI of the newly created object'
content:
application/json; charset=utf-8:
schema:
@ -3820,7 +3855,7 @@ paths:
Location:
schema:
type: string
description: URL to retrieve the details for the new created share
description: URI to retrieve the details for the new created share
content:
application/json; charset=utf-8:
schema: