Swift send image to phoenix API server

I’m developing an iOS app with a server Phoenix. I implemented a feature to upload an image (jpeg) to my server to be send after to AWS S3.

  • Everything work as expected when I send only 1 request but when I’m sending several requests, each one upload an image I start to get the error see below (not for all requests just for some - 7 sent / 4 failed / 3 success )?

  • Also I notified when I got this error, iOS need to keep uploading the all file to get at the end the error, even if the error have been print on the console 1min ago. I do not know if it an issue with Alamofire or Plug ?

Console error

    [debug] ** (Plug.Parsers.ParseError) malformed request, a MatchError exception was raised with message "no match of right hand side value: {:error, :timeout}"
        (cowboy) src/cowboy_req.erl:740: :cowboy_req.stream_multipart/2
        (cowboy) src/cowboy_req.erl:675: :cowboy_req.part/2
        (plug) lib/plug/adapters/cowboy/conn.ex:71: Plug.Adapters.Cowboy.Conn.parse_req_multipart/3
        (plug) lib/plug/parsers/multipart.ex:12: Plug.Parsers.MULTIPART.parse/5
        (plug) lib/plug/parsers.ex:215: Plug.Parsers.reduce/6
        (my_phoenix) lib/my_phoenix/endpoint.ex:1: MyPhoenix.Endpoint.phoenix_pipeline/1
        (my_phoenix) lib/plug/debugger.ex:123: MyPhoenix.Endpoint."call (overridable 3)"/2
        (my_phoenix) lib/my_phoenix/endpoint.ex:1: MyPhoenix.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

Mix file

defp deps do
    [{:phoenix, "~> 1.2.1"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:postgrex, "~> 0.11.2"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11.0"},
     {:cowboy, "~> 1.0"},
     {:comeonin, "~> 2.5"},
     {:geo, "~> 1.1.1"},
     {:httpoison, "~> 0.9.0"},
     {:excoveralls, "~> 0.5.5", only: :test},
     {:mailgun, "~> 0.1.2"},
     {:pigeon, "~> 0.9.0"},
     {:chatterbox, "~> 0.3.0-15-g401cef6", github: "joedevivo/chatterbox"},
     {:poison, "~> 2.1", override: true},
     {:arc_ecto, "~> 0.4.4"},
     {:arc, "0.5.3"},
     {:ex_aws, "~> 0.5.0"}]
end

Endpoint.ex

plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison,
    read_timeout: 120_000

Route API

scope "/api", MyPhoenix do
    pipe_through :api
    ...
    post "/upload/image", DataController, :upload
end

Swift code to send an image (with Alamofire)

    let url = "http://localhost:4000/api/upload"
    
    let header = ["Authorization": "Bearer 123QWE"]
    let fileType = ".jpg"
    let fileName = "\(NSDate().timeIntervalSince1970)".stringByReplacingOccurrencesOfString(".", withString: "")
    let dataImage = UIImageJPEGRepresentation(UIImage(named: "test")! 1)!
    let size = Double(imageData.length) / 1024.0
    let data: String = dataImage.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)

   Alamofire.upload(Alamofire.Method.POST, url, headers: header, multipartFormData: { (multipartFormData) in

        multipartFormData.appendBodyPart(data: fileType.dataUsingEncoding(NSUTF8StringEncoding)!, name: "file_type")
        multipartFormData.appendBodyPart(data: fileName.dataUsingEncoding(NSUTF8StringEncoding)!, name: "file_name")
        multipartFormData.appendBodyPart(data: "\(size)".dataUsingEncoding(NSUTF8StringEncoding)!, name: "size")
        multipartFormData.appendBodyPart(data: data.dataUsingEncoding(NSUTF8StringEncoding)!, name: "data")

        }, encodingCompletion: { encodingResult in
            switch encodingResult {
            case .Success(let upload, _, _):
                upload.responseJSON { response in
                    self.handleRequest(response)
                }
            case .Failure(_):
                self.handleRequest(nil)
            }
        }
    )

Phoenix API to upload image

  def upload(conn, %{"file_type" => file_type, "file_name" => file_name, "size" => image_size, "data" => data_param}) do
    changeset = Data.changeset(%Data{})
    case Repo.insert(changeset) do
      {:ok, data} ->
        upload_image(conn, %{id: data.id, file_type: file_type, file_name: file_name, size: image_size, data: data_param, type: Data.data_image})
      {:error, changeset} ->
        conn
          |> put_status(:unprocessable_entity)
          |> render(MyPhoenix.ChangesetView, "error.json", changeset: changeset)
    end
  end

  defp upload_image(conn, %{id: id, file_type: file_type, file_name: file_name, size: image_size, data: data_param, type: type}) do
        path_image = "/tmp/" <> Token.sign_data("#{id}_#{file_name}") <> file_type
        case File.write(path_image, :base64.mime_decode(data_param), [:binary]) do
          :ok ->
            upload_image_s3(conn, %{id: id, path_image: path_image, size: image_size, type: type})
          other ->
            conn
              |> put_status(:unprocessable_entity)
              |> render(MyPhoenix.ChangesetView, "error.json", ErrorData.init_error_upload_data)
        end
   end

Huh, that tells me that cowboy is timing out during the request, might need to raise some timeout value in the configuration somewhere. Looking at the file_uploads section of phoenix at http://www.phoenixframework.org/docs/file-uploads shows there is an argument of :read_timeout that defaults to 15 seconds, I wonder if that is it, maybe try upping it at the location that the above document gives? :slight_smile:

If the phone is really slow to upload then I could easily see an upload taking longer than 15 seconds.

Thanks for you advice but I forgot I added this flag too into my conf. I forget to add to my post…

In that case I’m stumped (and sadly I do not have time to research further at the moment), might need to wait for a genius like Jose or so to show up. ^.^

This will largely be speculation on my part, but one thing I note is that you are basing the “size” that you are sending off of the size of the image before it is Base64 encoded. I say this is speculation because I’m not sure how multipart MIME works but should that size be the size of the unencoded data, or the size of the encoded data?