Skip to content

Instantly share code, notes, and snippets.

@plvhx
Last active February 14, 2022 03:37
Show Gist options
  • Save plvhx/6c4300e7b20082b9ca127ff54d29e6c2 to your computer and use it in GitHub Desktop.
Save plvhx/6c4300e7b20082b9ca127ff54d29e6c2 to your computer and use it in GitHub Desktop.
range http header
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
package main
import (
"log"
"strconv"
"strings"
"time"
"regexp"
"encoding/json"
"io/ioutil"
"net/http"
)
var (
matchRegex = "^(?:bytes\\=)(?:[0-9]+\\-[0-9]+|[0-9]+\\-|\\-[0-9]+)(?:\\, (?:[0-9]+\\-[0-9]+))?(?:\\, (?:[0-9]+\\-[0-9]+))?$"
fetchRegex = "^(?:bytes\\=)([0-9]+\\-[0-9]+|\\-[0-9]+|[0-9]+\\-)(?:\\, ([0-9]+\\-[0-9]+))?(?:\\, ([0-9]+\\-[0-9]+))?$"
)
type Fault struct {
Message string `json:"message"`
Code int `json:"code"`
}
func isValidMimeType(mime string) bool {
if mime != "application/json" &&
mime != "application/x-www-form-urlencoded" {
return false
}
return true
}
func validateRangeHeader(value string) bool {
matched, _ := regexp.Match(matchRegex, []byte(value))
return matched
}
func parseRangeHeader(value string) [][]int {
re := regexp.MustCompile(fetchRegex)
res := re.FindAllStringSubmatch(value, -1)
tres := make([][]int, 0)
for k, v := range res[0] {
if k == 0 || v == "" {
continue
}
s := strings.Split(v, "-")
low, _ := strconv.ParseInt(s[0], 10, 32)
high, _ := strconv.ParseInt(s[1], 10, 32)
tres = append(tres, []int{int(low), int(high)})
}
return tres
}
func validateRangeHeaderValue(val [][]int, max int) bool {
for _, v := range val {
if v[0] < 0 || v[1] < v[0] {
return false
}
}
return true
}
func dispatchError(w http.ResponseWriter, message string, code int) {
buf, err := json.Marshal(Fault{Message: message, Code: code})
if err != nil {
w.Header().Set("X-Error-Timestamp", strconv.FormatInt(time.Now().Unix(), 10))
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
w.Header().Set("X-Error-Timestamp", strconv.FormatInt(time.Now().Unix(), 10))
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(append(buf, 0x0a))
}
func rangeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
dispatchError(w, "HTTP method should be POST.", http.StatusMethodNotAllowed)
return
}
if !isValidMimeType(r.Header.Get("Content-Type")) {
dispatchError(w, "Invalid mime type.", http.StatusUnsupportedMediaType)
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
dispatchError(w, err.Error(), http.StatusInternalServerError)
return
}
val := r.Header.Get("Range")
if val == "" {
dispatchError(w, "\"Range\" header must be exist.", http.StatusBadRequest)
return
}
if !validateRangeHeader(val) {
dispatchError(w, "\"Range\" header value is invalid.", http.StatusPreconditionFailed)
return
}
max := len(buf)
res := parseRangeHeader(val)
valid := validateRangeHeaderValue(res, max)
if !valid {
dispatchError(w, "Invalid range value.", http.StatusRequestedRangeNotSatisfiable)
return
}
rres := make([]byte, 0)
for _, v := range res {
if v[1] <= max {
rres = append(rres, buf[v[0]:v[1]]...)
}
}
// append trailing newline on fetched response.
rres = append(rres, 0x0a)
// set http status code to 206 (partial content).
w.WriteHeader(http.StatusPartialContent)
_, err = w.Write(rres)
if err != nil {
dispatchError(w, err.Error(), http.StatusInternalServerError)
return
}
}
func main() {
http.HandleFunc("/range", rangeHandler)
log.Fatal(http.ListenAndServe(":9000", nil))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment