github.com/aavshr/aws-sdk-go@v1.41.3/example/service/s3/presignURL/server/server.go (about)

     1  //go:build example
     2  // +build example
     3  
     4  package main
     5  
     6  import (
     7  	"encoding/json"
     8  	"flag"
     9  	"fmt"
    10  	"net"
    11  	"net/http"
    12  	"os"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/aavshr/aws-sdk-go/aws"
    18  	"github.com/aavshr/aws-sdk-go/aws/endpoints"
    19  	"github.com/aavshr/aws-sdk-go/aws/session"
    20  	"github.com/aavshr/aws-sdk-go/service/s3"
    21  	"github.com/aavshr/aws-sdk-go/service/s3/s3iface"
    22  	"github.com/aavshr/aws-sdk-go/service/s3/s3manager"
    23  )
    24  
    25  // server.go is an example of a service that vends lists for requests for presigned
    26  // URLs for S3 objects. The service supports two S3 operations, "GetObject" and
    27  // "PutObject".
    28  //
    29  // Example GetObject request to the service for the object with the key "MyObjectKey":
    30  //
    31  //   curl -v "http://127.0.0.1:8080/presign/my-object/key?method=GET"
    32  //
    33  // Example PutObject request to the service for the object with the key "MyObjectKey":
    34  //
    35  //   curl -v "http://127.0.0.1:8080/presign/my-object/key?method=PUT&contentLength=1024"
    36  //
    37  // Use "--help" command line argument flag to see all options and defaults.
    38  //
    39  // Usage:
    40  //   go run -tags example service.go -b myBucket
    41  func main() {
    42  	addr, bucket, region := loadConfig()
    43  
    44  	// Create a AWS SDK for Go Session that will load credentials using the SDK's
    45  	// default credential change.
    46  	sess := session.Must(session.NewSession())
    47  
    48  	// Use the GetBucketRegion utility to lookup the bucket's region automatically.
    49  	// The service.go will only do this correctly for AWS regions. For AWS China
    50  	// and AWS Gov Cloud the region needs to be specified to let the service know
    51  	// to look in those partitions instead of AWS.
    52  	if len(region) == 0 {
    53  		var err error
    54  		region, err = s3manager.GetBucketRegion(aws.BackgroundContext(), sess, bucket, endpoints.UsWest2RegionID)
    55  		if err != nil {
    56  			exitError(fmt.Errorf("failed to get bucket region, %v", err))
    57  		}
    58  	}
    59  
    60  	// Create a new S3 service client that will be use by the service to generate
    61  	// presigned URLs with. Not actual API requests will be made with this client.
    62  	// The credentials loaded when the Session was created above will be used
    63  	// to sign the requests with.
    64  	s3Svc := s3.New(sess, &aws.Config{
    65  		Region: aws.String(region),
    66  	})
    67  
    68  	// Start the server listening and serve presigned URLs for GetObject and
    69  	// PutObject requests.
    70  	if err := listenAndServe(addr, bucket, s3Svc); err != nil {
    71  		exitError(err)
    72  	}
    73  }
    74  
    75  func loadConfig() (addr, bucket, region string) {
    76  	flag.StringVar(&bucket, "b", "", "S3 `bucket` object should be uploaded to.")
    77  	flag.StringVar(&region, "r", "", "AWS `region` the bucket exists in, If not set region will be looked up, only valid for AWS Regions, not AWS China or Gov Cloud.")
    78  	flag.StringVar(&addr, "a", "127.0.0.1:8080", "The TCP `address` the server will be started on.")
    79  	flag.Parse()
    80  
    81  	if len(bucket) == 0 {
    82  		fmt.Fprintln(os.Stderr, "bucket is required")
    83  		flag.PrintDefaults()
    84  		os.Exit(1)
    85  	}
    86  
    87  	return addr, bucket, region
    88  }
    89  
    90  func listenAndServe(addr, bucket string, svc s3iface.S3API) error {
    91  	l, err := net.Listen("tcp", addr)
    92  	if err != nil {
    93  		return fmt.Errorf("failed to start service listener, %v", err)
    94  	}
    95  
    96  	const presignPath = "/presign/"
    97  
    98  	// Create the HTTP handler for the "/presign/" path prefix. This will handle
    99  	// all requests on this path, extracting the object's key from the path.
   100  	http.HandleFunc(presignPath, func(w http.ResponseWriter, r *http.Request) {
   101  		var u string
   102  		var err error
   103  		var signedHeaders http.Header
   104  
   105  		query := r.URL.Query()
   106  
   107  		var contentLen int64
   108  		// Optionally the Content-Length header can be included with the signature
   109  		// of the request. This is helpful to ensure the content uploaded is the
   110  		// size that is expected. Constraints like these can be further expanded
   111  		// with headers such as `Content-Type`. These can be enforced by the service
   112  		// requiring the client to satisfying those constraints when uploading
   113  		//
   114  		// In addition the client could provide the service with a SHA256 of the
   115  		// content to be uploaded. This prevents any other third party from uploading
   116  		// anything else with the presigned URL
   117  		if contLenStr := query.Get("contentLength"); len(contLenStr) > 0 {
   118  			contentLen, err = strconv.ParseInt(contLenStr, 10, 64)
   119  			if err != nil {
   120  				fmt.Fprintf(os.Stderr, "unable to parse request content length, %v", err)
   121  				http.Error(w, err.Error(), http.StatusBadRequest)
   122  				return
   123  			}
   124  		}
   125  
   126  		// Extract the object key from the path
   127  		key := strings.Replace(r.URL.Path, presignPath, "", 1)
   128  		method := query.Get("method")
   129  
   130  		switch method {
   131  		case "PUT":
   132  			// For creating PutObject presigned URLs
   133  			fmt.Println("Received request to presign PutObject for,", key)
   134  			sdkReq, _ := svc.PutObjectRequest(&s3.PutObjectInput{
   135  				Bucket: aws.String(bucket),
   136  				Key:    aws.String(key),
   137  
   138  				// If ContentLength is 0 the header will not be included in the signature.
   139  				ContentLength: aws.Int64(contentLen),
   140  			})
   141  			u, signedHeaders, err = sdkReq.PresignRequest(15 * time.Minute)
   142  		case "GET":
   143  			// For creating GetObject presigned URLs
   144  			fmt.Println("Received request to presign GetObject for,", key)
   145  			sdkReq, _ := svc.GetObjectRequest(&s3.GetObjectInput{
   146  				Bucket: aws.String(bucket),
   147  				Key:    aws.String(key),
   148  			})
   149  			u, signedHeaders, err = sdkReq.PresignRequest(15 * time.Minute)
   150  		default:
   151  			fmt.Fprintf(os.Stderr, "invalid method provided, %s, %v\n", method, err)
   152  			err = fmt.Errorf("invalid request")
   153  		}
   154  
   155  		if err != nil {
   156  			http.Error(w, err.Error(), http.StatusBadRequest)
   157  			return
   158  		}
   159  
   160  		// Create the response back to the client with the information on the
   161  		// presigned request and additional headers to include.
   162  		if err := json.NewEncoder(w).Encode(PresignResp{
   163  			Method: method,
   164  			URL:    u,
   165  			Header: signedHeaders,
   166  		}); err != nil {
   167  			fmt.Fprintf(os.Stderr, "failed to encode presign response, %v", err)
   168  		}
   169  	})
   170  
   171  	fmt.Println("Starting Server On:", "http://"+l.Addr().String())
   172  
   173  	s := &http.Server{}
   174  	return s.Serve(l)
   175  }
   176  
   177  // PresignResp provides the Go representation of the JSON value that will be
   178  // sent to the client.
   179  type PresignResp struct {
   180  	Method, URL string
   181  	Header      http.Header
   182  }
   183  
   184  func exitError(err error) {
   185  	fmt.Fprintln(os.Stderr, err.Error())
   186  	os.Exit(1)
   187  }