简体   繁体   English

无法使用Angular将文件上传到Rails服务器

[英]Not able to upload a file to Rails server with Angular

So I am using the gem 'angular-file-upload-rails' which installs me this Angular plugin: Angular File Ipload 所以我正在使用gem'angular gem 'angular-file-upload-rails' ,它为我安装了这个Angular插件: Angular File Ipload

Now the code I am using currently to upload my file looks like this: 现在,我当前用于上传文件的代码如下所示:

HTML: HTML:

<form ng-controller="MediumNewCtrl">
    <input type="file" ng-file-select="upload2($files)" multiple>
</form>

Coffescript: Coffescript:

$scope.upload2 = ($file) ->
        console.log($file[0])
        fileReader = new FileReader()
        fileReader.readAsArrayBuffer($file[0])
        fileReader.onload = (e) ->
            $upload.http(
                url: "/media.json"
                headers: 'Content-Type': $file[0].type
                data: medium: {text: 'text', image_video: e.target.result}
            ).progress((evt) ->
                console.log "percent: " + parseInt(100.0 * evt.loaded / evt.total)
                return
            ).success((data, status, headers, config) ->
                # file is uploaded successfully
                console.log data

            ).error((data) ->
                console.log 'Error'
            )

And now when I look at what my server responded, I see this: 现在,当我查看服务器响应时,会看到以下内容:

Started POST "/media.json" for 127.0.0.1 at 2014-12-12 20:19:10 +0200
Processing by Content::MediaController#create as JSON
  User Load (0.8ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 1  ORDER BY "users"."id" ASC LIMIT 1

{"action"=>"create", "controller"=>"content/media", "format"=>"json"}
Completed 400 Bad Request in 3ms

ActionController::ParameterMissing - param is missing or the value is empty: medium:

Is the problem in the fact that I format it as json? 问题是我将其格式化为json吗? But shouldnt atleast the text params be passed to the controller? 但是,是否应至少将文本参数传递给控制器​​?

I cannot use the html Post too because the nature of my application is so that it will intercept all HTML requests when you log in. 我也不能使用html Post,因为我的应用程序的性质是这样,以便您登录时它将拦截所有HTML请求。

Also maybe worth nothing that I use paperclip to manage my uploads for me. 另外,使用paperclip为我管理上传内容可能一文不值。 So I probably have to get the file sent into a proper format too for it? 因此,我可能也必须将文件发送为正确的格式吗?

It looks like you are using the 'upload right away' pattern. 您似乎正在使用“立即上传”模式。 Here is a complete example for future seekers: 这是面向未来求职者的完整示例:

app/views/static-pages/index.html: 应用程序/视图/静态页/ index.html的:

<div ng-app='myApp'>

  <h1>StaticPages#index</h1>
  <p>Find me in: app/views/static_pages/index.html.erb</p>
  <hr>

  <div ng-controller="FileUploadCtrl">
    <input type="file" 
      ng-file-select=""
      ng-model='selectedFiles' 
      ng-file-change="myUpload(selectedFiles)" 
      ng-multiple="true">
  </div>

</div>

app/assets/javascripts/main.js.coffee: 应用程序/资产/ Java脚本/ main.js.coffee:

@app = angular.module 'myApp', ['angularFileUpload']

app/assets/javascripts/FileUploadCtrl.js.coffee: 应用程序/资产/ Java脚本/ FileUploadCtrl.js.coffee:

@app.controller 'FileUploadCtrl', [
  '$scope', 
  '$upload', 
  '$timeout', 
  ($scope, $upload, $timeout) ->

    $scope.myUpload = (files) ->
      len = files.length
      i = 0
      fileReader = undefined
      csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')

      for file in files
        fileReader = new FileReader()

        #-------
        fileReader.onload = (e) ->

          #Define function for timeout, e.g. $timeout(timeout_func, 5000) 
          timeout_func = ->
            file.upload = $upload.http {
              url: "/static_pages/upload",
              method: 'POST',
              headers: {
                'Content-Type': file.type,
                'X-CSRF-TOKEN': csrf_token
              },
              data: e.target.result #the file's contents as an ArrayBuffer
            }

            file.upload.then(
              (success_resp) -> file.result = success_resp.data,  #response from server (=upload.html)
              (failure_resp) -> 
                if failure_resp.status > 0
                  $scope.errorMsg = "#{failure_resp.status}: #{response.data}"
            )

            file.upload.progress( (evt) ->
              file.progress = Math.min 100, parseInt(100.0 * evt.loaded / evt.total)
            )
          #end of timeout_func

          $timeout timeout_func, 5000 

        #end of FileReader.onload

        fileReader.readAsArrayBuffer file
]

Note: In the code above, I had to add the csrf lines because in app/views/layouts/application.rb, I have this: 注意:在上面的代码中,我必须添加csrf行,因为在app / views / layouts / application.rb中,我具有以下内容:

<%= csrf_meta_tags %>

which causes rails to add a csrf token to each web page. 这会导致Rails向每个网页添加csrf令牌。 angular-file-upload was causing rails CSRF Errors , so I had to retrieve the csrf token and add it to the request headers. angular-file-upload导致rails CSRF Errors ,所以我不得不检索csrf令牌并将其添加到请求标头中。

app/assets/javascripts/application.js: 应用程序/资产/ Java脚本/ application.js中:

//I removed: 
//     require turbolinks 
//for angular app
//
//= require jquery
//= require jquery_ujs
//
//The 'require_tree .' adds all the files in some random
//order, but for AngularJS the order is important:
//
//= require angular
//= require angular-file-upload-all
//
//And for some inexplicable reason, this is needed:
//= require main
//I would think 'require_tree .' would be enough for that file.
//
//= require_tree .

I didn't use gems for angular or angular-file-upload. 我没有使用gem进行角度上传或角度文件上传。 I just copied the AngularJS code into a file named angular.js which I put inside app/assets/javascripts. 我只是将AngularJS代码复制到名为angular.js的文件中,并将其放入app / assets / javascripts中。 Similarly, I copied the code in angular-file-upload-all into app/assets/javascripts/angular-file-upload-all.js 同样,我将angular-file-upload-all中的代码复制到app / assets / javascripts / angular-file-upload-all.js中

app/controllers/static_pages_controller.rb: 应用程序/控制器/ static_pages_controller.rb:

class StaticPagesController < ApplicationController

  def index
  end

  def upload
    puts "****PARAMS:"
    p params 

    puts "****body of request: #{request.body.read.inspect}"  #inspect => outputs "" for empty body rather than nothing
    puts "****Content-Type: #{request.headers['Content-Type']}"

    render nothing: true
  end  

end

config/routes.rb: 配置/ routes.rb文件:

Test1::Application.routes.draw do
  root "static_pages#index" 
  post "static_pages/upload"

As far as I can tell the data: key needs to be the contents of the file (as an ArrayBuffer). 据我所知, data:键必须是文件的内容(作为ArrayBuffer)。 To get rails to insert additional data in the params hash, you could use the url, for example 要使rails在params哈希中插入其他数据,可以使用url,例如

url: "/media.json" + '?firstName=Kaspar 

On the server side, the only way I could access the file was using request.body.read and the headers with request.headers['Content-Type'] . 在服务器端,访问文件的唯一方法是使用request.body.read和带有request.headers['Content-Type']的标头。 What did you end up doing? 你最终做了什么?

Also, I found two problems with file.type here: 另外,我在这里发现了file.type的两个问题:

headers: {
  'Content-Type': file.type,

1) For some reason, neither FireFox nor Chrome can determine the file type of a .json file , so file.type ends up being a blank string: "" . 1)由于某些原因,既不火狐也不铬可以确定的文件类型.json文件 ,所以file.type最终被一个空字符串: "" Rails then enters the file's contents as a key in the params hash. 然后,Rails在params哈希中输入文件的内容作为 Huh? 咦?

If you tack .json onto the end of the url: 如果将.json粘贴到网址末尾:

url: "/static_pages/upload.json",

...then Rails will parse the body of the request as JSON and enter the key/value pairs in the params hash. ...然后,Rails将请求的主体解析为JSON,并在params哈希中输入键/值对。 But adding .json to the url doesn't make the code very general because it prevents other file types from being processed correctly. 但是将.json添加到url不会使代码变得非常通用,因为它会阻止其他文件类型的正确处理。

Here is a more general solution for uploading .json files: 这是上传.json文件的更通用的解决方案:

  for file in files
    file_type = file.type

    if file_type is ''  #is => ===
      [..., file_ext] = file.name.split '.'

      if file_ext is 'json' 
        file_type = 'application/json'

...then later in the code: ...然后在代码后面:

 headers: {
    'Content-Type': file_type, #instead of file.type

2) However, there is still a closure problem in the original code, which needs to be corrected in order for multiple file selections to work correctly. 2)但是,原始代码中仍然存在关闭问题 ,需要纠正此问题才能使多个文件选择正常工作。 If you select multiple files, then the file_type for all the files will end up being the file_type of the last file. 如果选择多个文件,则所有文件的file_type将最终成为最后一个文件的file_type。 For instance, if you select a .txt file and a .json file, then both files will have the type of the second file, ie application/json . 例如,如果您选择.txt文件和.json文件,则两个文件都将具有第二个文件的类型,即application/json That's problematic because rails will try to parse the body of the text file as JSON, which will produce the error ActionDispatch::ParamsParser::ParseError . 这是有问题的,因为rails会尝试将文本文件的主体解析为JSON,这将产生错误ActionDispatch::ParamsParser::ParseError

To correct the closure problem, one well known solution is to define a wrapper function around fileReader.onload(). 为了纠正关闭问题,一种众所周知的解决方案是围绕fileReader.onload()定义包装函数。 Coffeescript has a syntax that makes adding a wrapper function especially pain free: Coffeescript的语法使添加包装器函数特别轻松:

    do (file_type) ->  #wrapper function, which `do` immediately executes sending it the argument file_type
      fileReader.onload = (e) ->
        ...
        ...

By adding one line, you can fix the shared variable problem. 通过添加一行,可以解决共享变量问题。 For details on what that does, go to the coffeescript home page and search the page for: do keyword . 有关执行该操作的详细信息,请转到coffeescript主页并在页面搜索: do keyword

app/assets/javascripts/FileUploadCtrl.js.coffee: 应用程序/资产/ Java脚本/ FileUploadCtrl.js.coffee:

@app.controller 'FileUploadCtrl', [
  '$scope', 
  '$upload', 
  '$timeout', 
  ($scope, $upload, $timeout) ->

    $scope.myUpload = (files) ->
      len = files.length
      i = 0
      fileReader = undefined
      csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')

      for file in files
        #console.log file
        file_type = file.type
        #console.log file_type

        if file_type is '' 
          [..., file_ext] = file.name.split '.'
          #console.log file_ext
          if file_ext is 'json'
            file_type = 'application/json'
            #console.log "Corrected file_type: " + file_type


        fileReader = new FileReader()

        #-------
        do (file_type) ->
          fileReader.onload = (e) ->

            #Define function for timeout, e.g. $timeout(timeout_func, 5000) 
            timeout_func = ->

              file.upload = $upload.http( 
                url: "/static_pages/upload"
                method: 'POST'
                headers: 
                  'Content-Type': file_type 
                  'X-CSRF-TOKEN': csrf_token
                data: e.target.result, #file contents as ArrayBuffer
              )

              file.upload.then(
                (success_resp) -> file.result = success_resp.data,  #response from server 
                (failure_resp) -> 
                  if failure_resp.status > 0
                    $scope.errorMsg = "#{failure_resp.status}: #{response.data}"
              )

              file.upload.progress (evt) ->
                file.progress = Math.min 100, parseInt(100.0 * evt.loaded / evt.total)
            #end of timeout_func

            $timeout timeout_func, 5000 

          #end of FileReader.onload

        fileReader.readAsArrayBuffer file
]

Finally, in this code, 最后,在这段代码中

data: e.target.result

...the entity returned by e.target.result is an ArrayBuffer , and I wasn't able to figure out how to modify that to add additional data. ...由e.target.result返回的实体是ArrayBuffer ,我无法弄清楚如何修改它以添加其他数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM