LoginSignup
23
22

More than 5 years have passed since last update.

AlamofireでRails+GrapeなWebAPIに対してバイナリファイルを送信する

Last updated at Posted at 2014-12-20

なんかいろいろはまったのでメモ。
まずポイントとして、

  • Alamofireは multipart/form-data に未対応(2014/12/21時点)
  • AlamofireでファイルアップロードするためのAPIは二つあるが、NSDataを引数にとる方はどうもバグっている。NSURL(Filepath)を引数にとるほうを利用する。
  • Grapeで multipart/form-data ではなくバイナリデータを受け取る場合はちょっと細工が必要

このあたりでしょうか。

Alamofire側のソース

Alamofire-SwiftyJSON 使ってます。

Api.swift

//
//  Api.swift
//
//  Created by Takatomo Okitsu on 2014/12/09.
//  Copyright (c) 2014年 Takatomo Okitsu. All rights reserved.
//

import Foundation
import Alamofire

class Api: NSObject{

    class func upload(
        path: String,
        filePath: NSURL,
        success: (NSURLRequest, NSHTTPURLResponse?, JSON) -> Void,
        invalid: (NSURLRequest, NSHTTPURLResponse?, ApiError) -> Void,
        failure: (NSURLRequest, NSHTTPURLResponse?, NSError?) -> Void) -> Void
    {
        let method:Alamofire.Method = .POST
        var url:String = makeUrl(method, path: path, isBinary: true)

        Alamofire.upload(method, url, filePath)
            .responseSwiftyJSON { (request, response, json, error) in
                if error != nil {
                    failure(request, response, error)
                } else {
                    if json["error"] != nil {
                        var apiError = ApiError(error: json["error"].object, code: 0, response: json)
                        invalid(request, response, apiError)
                    } else {
                        success(request, response, json)
                    }
                }
        }
    }

    class func makeUrl(method:Alamofire.Method, path:String?, isBinary:Bool? = false) -> String{
        var urlString = "http://localhost:3000"
        if path != nil && !isBinary! {
            urlString += path! + ".json"
        } else if isBinary! {
            urlString += path! + ".binary" // バイナリファイルを送る場合は、URLの最後に.binaryを付与する
        }
        return urlString;
    }
}

呼び出し側

        let image:UIImage = ...
        // alamofire workaround...
        // NSURLを生成するために、いったんファイルに書き出す。
        let data:NSData = NSData(data: UIImageJPEGRepresentation(image, 1))
        let tmpFilePath:String = NSTemporaryDirectory() + "/icon_tmp.jpg";
        data.writeToFile(tmpFilePath, atomically: true)
        Api.upload("/api/v1/images/image", filePath: NSURL(fileURLWithPath: tmpFilePath)!,
            success: { (request, response, json) -> Void in
                success(json)
            }, invalid: invalid, failure: failure)

Rails+Grape側のコード

こんな感じでいけました。

Images.rb

module V1
  class Images < Grape::API
    # ContentTypeがapplication/octet-streamの場合の定義
    content_type :binary, 'application/octet-stream'
    format :binary

    helpers do
      def uploaded_image
        data = env['api.request.input']

        temp_img_file = Tempfile.new('image')
        temp_img_file.binmode
        temp_img_file << data
        temp_img_file.rewind
        attachment = {
          :filename => 'image.jpg',
          :tempfile => temp_img_file
        }

        return ActionDispatch::Http::UploadedFile.new(attachment)
      end
    end

    resource :images do
      post :image do
        content_type "application/json"

        @user = current_user
        @user.image = uploaded_image //ここではCareerwWaveつかってる前提で書いてます。
        if @user.save
          return {
            image: {
              small: @user.image.small.url,
              medium: @user.image.medium.url,
              large: @user.image.large.url
            }
          }
        else
          return error!(@user.errors.full_messages, 200)
        end
      end
    end
  end
end

v1.rb

module V1
  class V1 < Grape::API
    version 'v1', using: :path
    mount V1::Images
  end
end

api.rb

module API
  class Base < Grape::API
    prefix 'api'
    format :json
    formatter :json, Grape::Formatter::Jbuilder
    rescue_from :all, :backtrace => true

    mount V1::V1
  end
end

結論

Alamofireはマルチパート使えるようになるまで利用するのやめたほうが何かと楽。サーバもクライアントも両方ともめんどい

23
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
22