Skip to content
Snippets Groups Projects
handler.go 8.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • OZGCloud's avatar
    OZGCloud committed
    /*
     * Copyright (C) 2023-2024
     * Das Land Schleswig-Holstein vertreten durch den
     * Ministerpräsidenten des Landes Schleswig-Holstein
     * Staatskanzlei
     * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
     *
     * Lizenziert unter der EUPL, Version 1.2 oder - sobald
     * diese von der Europäischen Kommission genehmigt wurden -
     * Folgeversionen der EUPL ("Lizenz");
     * Sie dürfen dieses Werk ausschließlich gemäß
     * dieser Lizenz nutzen.
     * Eine Kopie der Lizenz finden Sie hier:
     *
     * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
     *
     * Sofern nicht durch anwendbare Rechtsvorschriften
     * gefordert oder in schriftlicher Form vereinbart, wird
     * die unter der Lizenz verbreitete Software "so wie sie
     * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
     * ausdrücklich oder stillschweigend - verbreitet.
     * Die sprachspezifischen Genehmigungen und Beschränkungen
     * unter der Lizenz sind dem Lizenztext zu entnehmen.
     */
    
    package server
    
    import (
    
    	pb "antragsraum-proxy/gen/go"
    
    OZGCloud's avatar
    OZGCloud committed
    	"context"
    	"fmt"
    	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    
    	"google.golang.org/grpc"
    
    OZGCloud's avatar
    OZGCloud committed
    	"google.golang.org/grpc/codes"
    
    	"google.golang.org/grpc/credentials/insecure"
    
    OZGCloud's avatar
    OZGCloud committed
    	"google.golang.org/grpc/status"
    
    OZGCloud's avatar
    OZGCloud committed
    	"google.golang.org/protobuf/encoding/protojson"
    
    	"mime/multipart"
    
    OZGCloud's avatar
    OZGCloud committed
    	"net/http"
    )
    
    
    	fileFormKey          = "file"
    	vorgangIdFormKey     = "vorgangId"
    	contentTypeHeaderKey = "Content-Type"
    	maxFileSizeInMB      = 150
    	fileChunkSizeInByte  = 1024 * 1024
    
    OZGCloud's avatar
    OZGCloud committed
    
    
    
    OZGCloud's avatar
    OZGCloud committed
    func RegisterHomeEndpoint(mux *runtime.ServeMux) {
    	err := mux.HandlePath("GET", "/", func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
    		defer func() {
    			if err := recover(); err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    				log.WithError(err).Error("failed to recover: %v", err)
    
    OZGCloud's avatar
    OZGCloud committed
    				http.Error(w, fmt.Sprintf("failed to recover: %v", err), http.StatusInternalServerError)
    			}
    		}()
    	})
    
    	if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    	    log.WithError(err).Error("failed to register home endpoint: %v", err)
    
    OZGCloud's avatar
    OZGCloud committed
    func RegisterAntragraumEndpoints(ctx context.Context, mux *runtime.ServeMux, grpcUrl string) {
    	opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
    	err := pb.RegisterAntragraumServiceHandlerFromEndpoint(ctx, mux, grpcUrl, opts)
    	if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    	    log.WithError(err).Error("failed to register antragraum endpoint: %v", err)
    
    OZGCloud's avatar
    OZGCloud committed
    	}
    }
    
    func RegisterCommandEndpoints(ctx context.Context, mux *runtime.ServeMux, grpcUrl string) {
    	opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
    	err := pb.RegisterCommandServiceHandlerFromEndpoint(ctx, mux, grpcUrl, opts)
    	if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    	    log.WithError(err).Error("failed to register command endpoint: %v", err)
    
    
    OZGCloud's avatar
    OZGCloud committed
    func RegisterUploadAttachmentEndpoint(mux *runtime.ServeMux) {
    	err := mux.HandlePath("POST", "/api/v1/file", attachmentUploadHandler)
    
    OZGCloud's avatar
    OZGCloud committed
    	if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    		log.WithError(err).Error("failed to register upload endpoint: %v", err)
    
    OZGCloud's avatar
    OZGCloud committed
    	}
    }
    
    OZGCloud's avatar
    OZGCloud committed
    func attachmentUploadHandler(w http.ResponseWriter, r *http.Request, _ map[string]string) {
    
    OZGCloud's avatar
    OZGCloud committed
    	if err := r.ParseMultipartForm(maxFileSizeInMB << 20); err != nil {
    		http.Error(w, "invalid multipart form", http.StatusBadRequest)
    		return
    	}
    
    OZGCloud's avatar
    OZGCloud committed
    	file, header, err := r.FormFile(fileFormKey)
    	if err != nil {
    		http.Error(w, "file retrieval error", http.StatusBadRequest)
    		return
    	}
    	defer file.Close()
    
    OZGCloud's avatar
    OZGCloud committed
    	client, stream, err := createGrpcStream(r)
    	if err != nil {
    		http.Error(w, err.Error(), http.StatusInternalServerError)
    		return
    	}
    	defer client.Close()
    
    OZGCloud's avatar
    OZGCloud committed
    	if err := sendFileMetadata(stream, r, header); err != nil {
    		http.Error(w, err.Error(), http.StatusInternalServerError)
    		return
    	}
    
    OZGCloud's avatar
    OZGCloud committed
    
    
    OZGCloud's avatar
    OZGCloud committed
    	if err := sendFileChunks(stream, file); err != nil {
    		http.Error(w, err.Error(), http.StatusInternalServerError)
    		return
    	}
    
    OZGCloud's avatar
    OZGCloud committed
    	resp, err := stream.CloseAndRecv()
    	if err != nil {
    		http.Error(w, "response error", http.StatusInternalServerError)
    		return
    	}
    
    OZGCloud's avatar
    OZGCloud committed
    	w.WriteHeader(http.StatusOK)
    	w.Write([]byte(resp.FileId))
    }
    
    func createGrpcStream(r *http.Request) (*grpc.ClientConn, pb.BinaryFileService_UploadBinaryFileAsStreamClient, error) {
    	target := GetGrpcServerUrl(r.Header.Get(GrpcAddressHeader), conf)
    	conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
    	if err != nil {
    		return nil, nil, fmt.Errorf("gRPC connection error")
    	}
    
    OZGCloud's avatar
    OZGCloud committed
    	client := pb.NewBinaryFileServiceClient(conn)
    	stream, err := client.UploadBinaryFileAsStream(r.Context())
    
    	if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    		return nil, nil, fmt.Errorf("stream creation error")
    
    OZGCloud's avatar
    OZGCloud committed
    
    	return conn, stream, nil
    
    OZGCloud's avatar
    OZGCloud committed
    func sendFileMetadata(stream pb.BinaryFileService_UploadBinaryFileAsStreamClient, r *http.Request, header *multipart.FileHeader) error {
    	meta := &pb.GrpcUploadBinaryFileRequest{
    		Request: &pb.GrpcUploadBinaryFileRequest_Metadata{
    			Metadata: &pb.GrpcUploadBinaryFileMetaData{
    				VorgangId:   r.FormValue(vorgangIdFormKey),
    				FileName:    header.Filename,
    				ContentType: header.Header.Get(contentTypeHeaderKey),
    			},
    		},
    	}
    
    	if err := stream.Send(meta); err != nil {
    		return fmt.Errorf("metadata send error")
    	}
    
    OZGCloud's avatar
    OZGCloud committed
    	return nil
    }
    
    func sendFileChunks(stream pb.BinaryFileService_UploadBinaryFileAsStreamClient, file multipart.File) error {
    	buf := make([]byte, fileChunkSizeInByte)
    	for {
    		n, err := file.Read(buf)
    		if err == io.EOF {
    			break
    		}
    
    		if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    			return fmt.Errorf("file read error")
    
    OZGCloud's avatar
    OZGCloud committed
    		req := &pb.GrpcUploadBinaryFileRequest{
    			Request: &pb.GrpcUploadBinaryFileRequest_FileContent{
    				FileContent: buf[:n],
    			},
    		}
    		if err = stream.Send(req); err != nil {
    			return fmt.Errorf("chunk send error")
    
    OZGCloud's avatar
    OZGCloud committed
    	}
    
    OZGCloud's avatar
    OZGCloud committed
    	return nil
    }
    
    OZGCloud's avatar
    OZGCloud committed
    func RegisterGetAttachmentContentEndpoint(mux *runtime.ServeMux) {
    	err := mux.HandlePath("POST", "/api/v1/file/content", getAttachmentContentHandler)
    
    OZGCloud's avatar
    OZGCloud committed
    	if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    		log.WithError(err).Error("failed to register get file endpoint: %v", err)
    
    
    OZGCloud's avatar
    OZGCloud committed
    	}
    }
    
    OZGCloud's avatar
    OZGCloud committed
    func getAttachmentContentHandler(w http.ResponseWriter, r *http.Request, _ map[string]string) {
    	requestPayload, err := getGetAttachmentContentRequestPayload(r)
    	if err != nil {
    		http.Error(w, "failed to read request body", http.StatusBadRequest)
    	}
    
    	fileBuffer, err := fetchFileFromGrpc(r.Context(), requestPayload, r.Header.Get(GrpcAddressHeader))
    
    OZGCloud's avatar
    OZGCloud committed
    	if err != nil {
    		http.Error(w, err.Error(), http.StatusInternalServerError)
    		return
    	}
    
    	w.Header().Set(contentTypeHeaderKey, "application/octet-stream")
    	w.WriteHeader(http.StatusOK)
    
    	_, err = fileBuffer.WriteTo(w)
    
    OZGCloud's avatar
    OZGCloud committed
    	if err != nil {
    		http.Error(w, err.Error(), http.StatusInternalServerError)
    	}
    }
    
    
    OZGCloud's avatar
    OZGCloud committed
    func getGetAttachmentContentRequestPayload(r *http.Request) (*pb.GrpcGetAttachmentContentRequest, error) {
    	body, err := io.ReadAll(r.Body)
    	if err != nil {
    		return nil, err
    	}
    	defer r.Body.Close()
    
    	var requestPayload pb.GrpcGetAttachmentContentRequest
    	if err = protojson.Unmarshal(body, &requestPayload); err != nil {
    		return nil, err
    	}
    
    	return &requestPayload, nil
    }
    
    func fetchFileFromGrpc(ctx context.Context, request *pb.GrpcGetAttachmentContentRequest, grpcAddress string) (*bytes.Buffer, error) {
    
    OZGCloud's avatar
    OZGCloud committed
    	var fileBuffer bytes.Buffer
    
    	target := GetGrpcServerUrl(grpcAddress, conf)
    	conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
    	if err != nil {
    		return nil, fmt.Errorf("gRPC connection error")
    	}
    	defer conn.Close()
    
    
    OZGCloud's avatar
    OZGCloud committed
    	client := pb.NewAntragraumServiceClient(conn)
    	clientStream, err := client.GetAttachmentContent(ctx, request)
    
    OZGCloud's avatar
    OZGCloud committed
    	if err != nil {
    		return nil, fmt.Errorf("stream creation error")
    	}
    
    	for {
    		resp, err := clientStream.Recv()
    		if err == io.EOF {
    			break
    		}
    
    		if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    			return nil, fmt.Errorf("error receiving file chunks")
    
    OZGCloud's avatar
    OZGCloud committed
    		_, err = fileBuffer.Write(resp.FileContent)
    
    		if err != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    			return nil, fmt.Errorf("error writing file data")
    
    OZGCloud's avatar
    OZGCloud committed
    	}
    
    	return &fileBuffer, nil
    }
    
    OZGCloud's avatar
    OZGCloud committed
    func ErrorHandler(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, r *http.Request, err error) {
    	st, ok := status.FromError(err)
    	if !ok {
    		runtime.DefaultHTTPErrorHandler(ctx, mux, marshaler, w, r, err)
    		return
    	}
    
    	statusCodeMap := map[codes.Code]int{
    		codes.InvalidArgument: http.StatusBadRequest,
    		codes.AlreadyExists:   http.StatusConflict,
    		codes.Unavailable:     http.StatusServiceUnavailable,
    	}
    
    	httpStatusCode := http.StatusInternalServerError
    
    	if code, exists := statusCodeMap[st.Code()]; exists {
    		httpStatusCode = code
    	}
    
    	w.Header().Set("Content-Type", marshaler.ContentType(r))
    	w.WriteHeader(httpStatusCode)
    
    	_, writeErr := w.Write([]byte(st.Message()))
    	if writeErr != nil {
    
    OZGCloud's avatar
    OZGCloud committed
    		log.WithError(err).Error("failed to handle grpc error: %v", writeErr)
    
    OZGCloud's avatar
    OZGCloud committed
    	}
    }