본문 바로가기
Programming/Go

Go 로 MinIO 에 파일 업다운로드

by TinKerBellBass 2022. 4. 6.
728x90
반응형

MinIO 설치는 다음 블로그 글을 참조.

 

Object Storage MinIO 설치

MinIO MinIO 는 설치형 Object Storage 로 AWS SDK 를 그대로 쓸수 있어 AWS 의 S3 대신 로컬에서 테스트 할 때 쓸 수 있는 유용하다. 설치 Mac OS 의 경우 Homebrew 를 통해 스탠드얼론으로 설치할 수 있지만 왠..

tinkerbellbass.tistory.com

 

파일 업로드

MinIO 에서 제공하는 SDK? API? 는 AWS SDK 와 동일해서

코드 변경 없이 환경변수 설정만으로 S3 의 테스트 Object Storage 로서

로컬, 개발 환경에서 사용할 수 있다.

정말 매력적이지 않는가?

 

MinIO 공홈에는 아직 aws-sdk-go-v1 가 안내되어 있다.

안정화된 최신 버전이 있는데 안 쓸 이유가 없기에

aws-sdk-go-v2 로 구현해 보았다.

 

AWS 연결과 S3 클라이언트 획득을 위한 코드를 구현하자.

S3 클라이언트를 획득하는 newS3Storage 메서드,

실행 환경에 따라 AWS 설정을 만드는 createAWSCfg 메서드,

실행 환경에 따라 버킷 이름을 획득하는 getBucketName 메서드.

v2 는 호스트 이름이 필요 없기에

awsConfig.WithEndpointResolverWithOptions(endPointResolver) 를 구현해서 MinIO 에 연결해야 한다.

func newS3Storage(env string) (*s3.Client, error) {
	awsCfg, err := createAWSCfg(env)
	if err != nil {
		return nil, err
	}

	s3Client := s3.NewFromConfig(awsCfg)

	return s3Client, nil
}

func createAWSCfg(env string) (aws.Config, error) {
	// TODO 하드코딩된 설정 값들을 환경변수에서 가져와서 세팅하게 변경해야 함
	switch env {
	case "dev":
		credential := credentials.NewStaticCredentialsProvider("accessKey", "secretKey", "")
		endPointResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
			return aws.Endpoint{
				PartitionID:       "aws",
				URL:               "http://localhost:9000",
				SigningRegion:     "ap-northeast-2",
				HostnameImmutable: true,
			}, nil
		})
		awsCfg, err := awsConfig.LoadDefaultConfig(
			context.TODO(),
			awsConfig.WithCredentialsProvider(credential),
			awsConfig.WithEndpointResolverWithOptions(endPointResolver),
		)
		if err != nil {
			return aws.Config{}, err
		}
		return awsCfg, nil
	case "prod":
		credential := credentials.NewStaticCredentialsProvider("awsAccessKey", "awsSecretKey", "")
		awsCfg, err := awsConfig.LoadDefaultConfig(
			context.TODO(),
			awsConfig.WithCredentialsProvider(credential),
			awsConfig.WithRegion("ap-northeast-2"),
		)
		if err != nil {
			return aws.Config{}, err
		}
		return awsCfg, nil
	default:
		return aws.Config{}, errors.New("env is not defined")
	}
}

func getBucketName(env string) string {
	switch env {
	case "dev":
		return "dev-bucket"
	case "prod":
		return "prod-bucket"
	default:
		return "dev-bucket"
	}
}

다음으로 파일 업로드를 담당하는 UploadFile 메서드.

KMS 로 암호화 해서 올리려면 KES 서버를 구축해야 한다고 공홈에 나와있다.

왠지 땡기지도 않고 귀찮아서 다음에 해보기로 하고 패스.

func UploadFile(file multipart.File, fileName string, env string) (*manager.UploadOutput, error) {
	s3Client, err := newS3Storage(env)
	if err != nil {
		return nil, err
	}
	uploader := manager.NewUploader(s3Client)

	result, err := uploader.Upload(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(getBucketName(env)),
		Key:    aws.String(fileName),
		Body:   file,
		// TODO minio kms 세팅이 복잡해서 시간날 때 구현해 볼 것
		//ServerSideEncryption: "aws:kms",
	})

	return result, err
}

 

파일 다운로드

AWS 연결과 S3 클라이언트 획득은 파일 업로드와 동일하기에 생략하고

파일 다운로드를 담당하는 DownloadFile 메서드.

func DownloadFile(objectKey string, env string) ([]byte, error) {
	s3Client, err := newS3Storage(env)
	if err != nil {
		return nil, err
	}

	buffer := manager.NewWriteAtBuffer([]byte{})
	downloader := manager.NewDownloader(s3Client)

	numBytes, err := downloader.Download(context.TODO(), buffer, &s3.GetObjectInput{
		Bucket: aws.String(getBucketName(env)),
		Key:    aws.String(objectKey),
	})
	if err != nil {
		return nil, err
	}

	if numBytes < 1 {
		return nil, errors.New("zero bytes written to memory")
	}

	return buffer.Bytes(), nil
}

 

Main

gorillar/mux 를 사용해서 파일 업로드, 다운로드 API 구현.

func main() {
	r := mux.NewRouter()

	r.HandleFunc("/upload", upload).Methods(http.MethodPost)
	r.HandleFunc("/download/{objectKey}", download).Methods(http.MethodGet)

	srv := &http.Server{
		Handler:      r,
		Addr:         "127.0.0.1:8080",
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	err := srv.ListenAndServe()
	if err != nil {
		panic(err)
	}
}

func upload(w http.ResponseWriter, r *http.Request) {
	file, fileHeader, err := r.FormFile("file")
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	defer file.Close()

	// TODO 환경에 따라 환경변수를 셋팅하여 env 값을 넣도록 수정할 것
	result, err := s3.UploadFile(file, fileHeader.Filename, "dev")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	err = json.NewEncoder(w).Encode(result)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func download(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	objectKey := vars["objectKey"]

	// TODO 환경에 따라 환경변수를 셋팅하여 env 값을 넣도록 수정할 것
	fileByte, err := s3.DownloadFile(objectKey, "dev")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)

	_, err = io.Copy(w, bytes.NewReader(fileByte))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

다 만들었으니 포스트맨으로 요청 보내보기.

업로드 성공!

다운로드 성공!

 

전체 소스는 https://github.com/Jongwon-Hyun/minio_test

728x90
반응형

댓글