사전 예방적 스토리지 호환 GraphQL: 직접 업로드

27316 단어 rubygraphqlrails
섹션 2 사용 가능

즐거운 사람의 즐거움 코드


나는 즐거운 사람이다.
이런 행복은 다차원적이다. 어떤 차원은 다른 차원보다 더 가치가 있다.
예를 들어, 나는 업무 중에 매우 기뻤다. 왜냐하면 나는 마침내 이런'첨단'프레임을 사용하여 처음부터 하나의 프로젝트를 구축할 기회가 생겼기 때문이다.😉) 기술, asRails 6.
믿든 안 믿든 상관없어. 난 이미 Rails 생산 프로젝트에서 5년 동안 일했어. 심지어 Rails 5를 접촉한 적이 없어. Rails 4밖에 없어!
"유류 Rails 응용 프로그램"은 저의 전문화입니다. (참고로 이것은 제가 곧 개최할 RailsConf 회의의 주제입니다.)
이 암흑시대들은 this January을 끝냈다. 나는 gem install rails --prerelease && rails new ***을 달렸다.
(실제로는 rails new *** -d postgresql --skip-action-mailbox --skip-action-text --skip-action-cable --skip-sprockets --skip-spring --skip-test --skip-bundle입니다.)
내가 하고 있는 프로젝트는 100% 새로운 코드 라이브러리가 아니다.Rails 4를 위해 처음 작성된 코드가 많습니다.
이것은 GraphQL API를 클라이언트(웹 및 모바일 응용 프로그램)의 주요 입구점으로 한다.
낡은 코드 라이브러리를 새로운 응용 프로그램에 이식하는 일부분으로, 우리는 CarrierWave에서 Active Storage으로 이전했다.체험이 순조롭다.비록 Active Storage는 some missing parts이 있지만 그 장점과 Rails 방식의 단순성을 가지고 있다.
주의: Active Storage의 초보자라면 1년 전에 제가 쓴 게시물 "Rails 5.2: Active Storage and beyond"을 보십시오.
따라서 이제는 액티브 저장소의 가장 현저한 장점인 direct uploads으로 전환할 때가 되었다.

유효한 저장 전 수명


우선 Rails 4에서 파일 업로드를 처리하는 방법을 알려드릴게요.GraphQL 규범과 graphql Ruby gem은 올바른 요리 파일을 업로드하는 방법을 지정하지 않았습니다.
open-source specification이 있는데 Ruby을 포함하여 서로 다른 언어의 실현이 있다.이것은 Upload 표량 유형을 묘사하고 Rack middleware 마술을 하여 업로드된 파일을 변수로 전달하고 약간 투명하다.
듣자니 "끼워 넣자마자 쓰기"인 것 같다이론상실천에서 이는'꽂기만 하면 n-fail-n-fix-n-fail-n-fix'로 바뀐다.
  • 잘못된 클라이언트 구현 (특히 React Native)
  • 비엄격한 Upload type(실제 대상 유형에 무관심)으로 인한 부작용
  • 아폴로 의존 (네, 안녕히 계세요!새 버전의 아폴로;하지만 이것은 또 다른 이야기다.
  • 사고 없음(no alarms)😉), 우리는 이런 해커 행위에서 벗어나 오래된 REST를 사용하여 파일을 업로드하기로 결정했다.
    여기에 직접 업로드하는 이벤트 저장소가 있습니다.

    부트 업로드🎥


    그나저나'바로 업로드'가 뭐예요?
    이 용어는 일반적으로 클라우드 저장소 서비스(예를 들어 Amazon S3)와 결합하여 사용하는데 그 의미는 다음과 같다. 클라이언트가 API 서버를 사용하여 생성한 증빙서류를 사용하여 파일을 클라우드 저장소에 직접 업로드하는 것이지 API 서버를 사용하여 파일을 업로드하는 것이 아니다.

    직접 업로드
    좋은 소식 – Active Storage는 직접 업로드를 처리하는 서버 측 API 및 프런트엔드 JS 클라이언트 out-of-the-box을 제공합니다.
    또 다른 좋은 소식은 이 API는 추상적이며 활성 저장소가 지원하는 service(파일 시스템, S3, GCloud, Azure)에 적용된다는 것이다.이것은 매우 좋습니다. 로컬에서 파일 시스템을 사용할 수 있습니다. 생산 과정에서 S3를 사용할 수 있습니다. 모든 if-s와 else--s가 필요하지 않습니다.
    그럼에도 불구하고 좋은 소식은 항상 나쁜 소식을 수반한다.나쁜 소식은 활성 저장소 (일반 Rails) 가 GraphQL에 대해 아무것도 모르고, 직접 업로드 자격 증명을 검색하기 위해 자체 RESTAPI에 의존한다는 것이다.
    GraphQL에서 이 모든 것을 실현하려면 어떻게 해야 합니까?
    우선 GraphQL API(mutation)를 사용하여 직접 업로드 자격 증명을 얻을 수 있습니다.
    그 다음으로 가능한 한 프레임워크의 자바스크립트 코드를 많이 다시 사용해서 바퀴의 중복 발명을 피하는 것이 좋다.

    createDirectUpload 변이...



    GraphiQL의 돌변 미리보기
    불행하게도 Rails는 서버 측에서 직접 실행된 문서를 업로드하지 않았습니다.
    source code, DirectUploadsController:
    def create
      blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args)
      render json: direct_upload_json(blob)
    end
    
    private
    
    def blob_args
      params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys
    end
    
    def direct_upload_json(blob)
      blob.as_json(root: false, methods: :signed_id).merge(direct_upload: {
        url: blob.service_url_for_direct_upload,
        headers: blob.service_headers_for_direct_upload
      })
    end
    
    checksum 파라미터를 보십시오. 이것은 활성 저장소에 숨겨진 보석 중 하나입니다. 내장된 파일 내용 검증입니다.
    클라이언트가 직접 업로드를 요청할 때 파일의 검증 및 (MD5 해시 인코딩 Base64) 을 지정할 수 있으며, 서비스 (예: 활성 메모리 자체 또는 S3) 는 나중에 이 검사 및 를 사용하여 업로드된 파일의 내용을 검증합니다.
    GraphQL로 돌아가겠습니다.
    GraphQL 돌연변이는 Rails 컨트롤러와 매우 유사하기 때문에 상기 코드를 돌연변이로 전환하는 것은 매우 간단하다.
    class CreateDirectUpload < GraphQL::Schema::Mutation
      class CreateDirectUploadInput < GraphQL::Schema::InputObject
        description "File information required to prepare a direct upload"
    
        argument :filename, String, "Original file name", required: true
        argument :byte_size, Int, "File size (bytes)", required: true
        argument :checksum, String, "MD5 file checksum as base64", required: true
        argument :content_type, String, "File content type", required: true
      end
    
      argument :input, CreateDirectUploadInput, required: true
    
      class DirectUpload < GraphQL::Schema::Object
        description "Represents direct upload credentials"
    
        field :url, String, "Upload URL", null: false
        field :headers, String,
              "HTTP request headers (JSON-encoded)",
              null: false
        field :blob_id, ID, "Created blob record ID", null: false
        field :signed_blob_id, ID,
              "Created blob record signed ID",
              null: false
      end
    
      field :direct_upload, DirectUpload, null: false
    
      def resolve(input:)
        blob = ActiveStorage::Blob.create_before_direct_upload!(input.to_h)
    
        {
          direct_upload: {
            url: blob.service_url_for_direct_upload,
            # NOTE: we pass headers as JSON since they have no schema
            headers: blob.service_headers_for_direct_upload.to_json,
            blob_id: blob.id,
            signed_blob_id: blob.signed_id
          }
        }
      end
    end
    
    
    # add this mutation to your Mutation type
    field :create_direct_upload, mutation: CreateDirectUpload
    
    이제 서버에서 직접 업로드 로드를 검색하려면 GraphQL 클라이언트가 다음 요청을 수행해야 합니다.
    mutation {
      createDirectUpload(input: {
        filename: "dev.to", # file name
        contentType: "image/jpeg", # file content type
        checksum: "Z3Yzc2Q5iA5eXIgeTJn", # checksum
        byteSize: 2019 # size in bytes
      }) {
        directUpload {
          signedBlobId
        }
      }
    }
    

    ...JavaScript도 있습니다.


    면책 성명: 아래 JS 구현은 단지 초도일 뿐, 아직 현실에서 테스트한 적이 없습니다(제 프로젝트에서 Rails의 JS 코드를 사용하지 않았기 때문).내가 검사한 것은 단지 그것의 번역일 뿐이다.
    파일을 업로드하려면 클라이언트가 다음 단계를 수행해야 합니다.
  • 파일 메타데이터 얻기(파일 이름, 크기, 내용 유형 및 검증 및)
  • API 요청을 통해 자격 증명 및 blob ID 직접 업로드 – createDirectUpload 변이
  • 은 자격 증명을 사용하여 파일을 업로드합니다(GraphQL, HTTP PUT 요청 제외).
  • 1단계와 3단계에서는 Rails와 함께 제공된 JS 라이브러리의 일부 코드를 다시 사용할 수 있습니다("@rails/activestorage"package.json에 추가하는 것을 잊지 마십시오).getFileMetadata 함수를 작성합니다.
    import { FileChecksum } from "@rails/activestorage/src/file_checksum";
    
    function calculateChecksum(file) {
      return new Promise((resolve, reject) => {
        FileChecksum.create(file, (error, checksum) => {
          if (error) {
            reject(error);
            return;
          }
    
          resolve(checksum);
        });
      });
    }
    
    
    export const getFileMetadata = (file) => {
      return new Promise((resolve) => {
        calculateChecksum(file).then((checksum) => {
          resolve({
            checksum,
            filename: file.name,
            content_type: file.type,
            byte_size: file.size
          });    
        });
      });
    };
    
    FileChecksum 클래스는 계산에 필요한 검사와 검사를 책임지고 DirectUpload 클래스의 활성 메모리에서 사용한다.
    이제 이 함수를 사용하여 GraphQL 쿼리 로드를 구성할 수 있습니다.
    // pseudo code
    getFileMetadata(file).then((input) => {
      return performQuery(
        CREATE_DIRECT_UPLOAD_QUERY,
        variables: { input }
      );
    });
    
    파일을 저장 서비스에 직접 업로드하는 함수를 작성할 때입니다!
    import { BlobUpload } from "@rails/activestorage/src/blob_upload";
    
    export const directUpload = (url, headers, file) => {
      const upload = new BlobUpload({ file, directUploadData: { url, headers } });
      return new Promise((resolve, reject) => {
        upload.create(error => {
          if (error) {
            reject(error);
          } else {
            resolve();
          }
        })
      });
    };
    
    전체 클라이언트 코드의 예는 다음과 같습니다.
    getFileMetadata(file).then((input) => {
      return performQuery(
        CREATE_DIRECT_UPLOAD_QUERY,
        variables: { input }
      ).then(({ directUpload: { url, headers, signedBlobId }) => {
        return directUpload(url, JSON.parse(headers), file).then(() => {
          // do smth with signedBlobId – our file has been uploaded!
        });
      });
    });
    
    우리가 해냈구나!새로운 Rails+GraphQL 프로젝트를 구축하는 데 도움이 되었으면 합니다.)

    For more realistic example, check out this snippet from our React Native application: https://gist.github.com/Saionaro/7ee0e2c02749e2729dc429c9e9bfa7f3


    어쨌든, 또는 signedBlobId를 어떻게 처리하는지


    응용 프로그램에서 서명blob id-attachProfileAvatar 변이를 어떻게 사용하는지 간단한 예를 들겠습니다.
    class AttachProfileAvatar < GraphQL::Schema::Mutation
      description <<~DESC
       Update the current user's avatar
       (by attaching a blob via signed ID)
      DESC
    
      argument :blob_id, String,
                "Signed blob ID generated via `createDirectUpload` mutation",
                required: true
    
      field :user, Types::User, null: true
    
      def resolve(blob_id:)
        # Active Storage retrieves the blob data from DB
        # using a signed_id and associates the blob with the attachment (avatar)
        current_user.avatar.attach(blob_id)
        {user: current_user}
      end
    end
    
    그렇습니다!
    https://evilmartians.com/chronicles에 대한 더 많은 개발 글을 읽으세요!

    좋은 웹페이지 즐겨찾기