Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away