greatape/greataped/server/http_context.go

235 wiersze
5.9 KiB
Go

package server
import (
"bytes"
"config"
. "contracts"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"path"
"server/mime"
"time"
"utility"
"utility/httpsig"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)
type httpServerContext struct {
underlyingContext *fiber.Ctx
request IRequest
response IResponse
stringUtil IStringUtil
httpClient *http.Client
}
func newContext(underlyingContext *fiber.Ctx) IContext {
return &httpServerContext{
underlyingContext: underlyingContext,
request: newRequest(underlyingContext),
response: newResponse(underlyingContext),
stringUtil: utility.NewStringUtil(),
httpClient: &http.Client{
Timeout: time.Second * 5,
},
}
}
func (context *httpServerContext) GUID() string {
return uuid.New().String()
}
func (context *httpServerContext) String(payload any) error {
return context.underlyingContext.SendString(payload.(string))
}
func (context *httpServerContext) Json(payload interface{}) error {
return context.underlyingContext.JSON(payload)
}
func (context *httpServerContext) Activity(payload interface{}) error {
data, err := json.Marshal(payload)
if err != nil {
return err
}
context.underlyingContext.Response().Header.Add("Content-Type", mime.ActivityJsonUtf8)
return context.underlyingContext.Send(data)
}
func (context *httpServerContext) File(key string) error {
// TODO: Compress the response
filePath := path.Join(config.UPLOAD_PATH, key)
return context.underlyingContext.SendFile(filePath)
}
func (context *httpServerContext) Nothing() error {
return context.underlyingContext.JSON(&struct{}{})
}
func (context *httpServerContext) Redirect(location string, status ...int) error {
return context.underlyingContext.Redirect(location, status...)
}
func (context *httpServerContext) Render(name string, bind interface{}, layouts ...string) error {
return context.underlyingContext.Render(name, bind, layouts...)
}
func (context *httpServerContext) Config() IConfig {
return nil
}
func (context *httpServerContext) Storage() IStorage {
return nil
}
func (context *httpServerContext) Request() IRequest {
return context.request
}
func (context *httpServerContext) Response() IResponse {
return context.response
}
func (context *httpServerContext) StringUtil() IStringUtil {
return context.stringUtil
}
func (context *httpServerContext) ParseJson(interface{}) IResult {
return nil
}
func (context *httpServerContext) ParseBodyAndValidate(body interface{}) error {
if err := context.underlyingContext.BodyParser(body); err != nil {
return fiber.ErrBadRequest
}
return utility.Validate(body)
}
func (context *httpServerContext) SaveFile(f *multipart.FileHeader, path string) error {
return context.underlyingContext.SaveFile(f, path)
}
func (context *httpServerContext) GetUser() uint {
id, _ := context.underlyingContext.Locals("USER").(uint)
return id
}
// Error create a server error with status code and message
func (context *httpServerContext) Error(code int, format string, args ...any) IServerError {
return newError(code, fmt.Sprintf(format, args...))
}
func (context *httpServerContext) BadRequest(format string, args ...any) IServerError {
return context.Error(StatusBadRequest, format, args...)
}
func (context *httpServerContext) NotFound(format string, args ...any) IServerError {
return context.Error(StatusNotFound, format, args...)
}
func (context *httpServerContext) InternalServerError(format string, args ...any) IServerError {
return context.Error(StatusInternalServerError, format, args...)
}
func (context *httpServerContext) Unauthorized(format string, args ...any) IServerError {
return context.Error(StatusUnauthorized, format, args...)
}
func (context *httpServerContext) Conflict(format string, args ...any) IServerError {
return context.Error(StatusConflict, format, args...)
}
func (context *httpServerContext) signRequest(keyId, privateKey string, data []byte, req *http.Request) error {
privKey, err := httpsig.ParseRsaPrivateKeyFromPemStr(privateKey)
if err != nil {
return err
}
signer := httpsig.NewRSASHA256Signer(keyId, privKey, []string{"Date", "Digest"})
if data != nil {
hasher := sha256.New()
hasher.Write(data)
sum := hasher.Sum(nil)
encodedHash := base64.StdEncoding.EncodeToString(sum)
digest := fmt.Sprintf("sha-256=%s", encodedHash)
req.Header.Set("Content-Type", mime.ActivityJsonUtf8)
req.Header.Set("Digest", digest)
}
req.Header.Set("Accept", mime.ActivityJson)
if err := signer.Sign(req); err != nil {
return err
}
return nil
}
func (context *httpServerContext) requestActivityStream(method, url, keyId, privateKey string, data []byte, output interface{}) error {
var reader io.Reader
if data != nil {
reader = bytes.NewBuffer(data)
}
req, err := http.NewRequest(method, url, reader)
if err != nil {
return err
}
if privateKey != "" {
if err := context.signRequest(keyId, privateKey, data, req); err != nil {
return err
}
}
if keyId == "activitystream" {
req.Header.Add("Accept", "application/activity+json")
}
res, err := context.httpClient.Do(req)
if err != nil {
return err
}
if res.Body != nil {
defer res.Body.Close()
}
if res.StatusCode != http.StatusOK &&
res.StatusCode != http.StatusAccepted {
return fmt.Errorf("%s", res.Status)
}
j, err := io.ReadAll(res.Body)
if err != nil {
return err
}
fmt.Println(string(j))
if output != nil {
if err := json.Unmarshal(j, output); err != nil {
return err
}
}
return nil
}
func (context *httpServerContext) GetActivityStream(url, keyId, privateKey string, data []byte, output interface{}) error {
return context.requestActivityStream(http.MethodGet, url, keyId, privateKey, data, output)
}
func (context *httpServerContext) PostActivityStream(url, keyId, privateKey string, data []byte, output interface{}) error {
return context.requestActivityStream(http.MethodPost, url, keyId, privateKey, data, output)
}