sendfile.rb 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. require 'rack/file'
  2. module Rack
  3. # = Sendfile
  4. #
  5. # The Sendfile middleware intercepts responses whose body is being
  6. # served from a file and replaces it with a server specific X-Sendfile
  7. # header. The web server is then responsible for writing the file contents
  8. # to the client. This can dramatically reduce the amount of work required
  9. # by the Ruby backend and takes advantage of the web server's optimized file
  10. # delivery code.
  11. #
  12. # In order to take advantage of this middleware, the response body must
  13. # respond to +to_path+ and the request must include an X-Sendfile-Type
  14. # header. Rack::File and other components implement +to_path+ so there's
  15. # rarely anything you need to do in your application. The X-Sendfile-Type
  16. # header is typically set in your web servers configuration. The following
  17. # sections attempt to document
  18. #
  19. # === Nginx
  20. #
  21. # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
  22. # but requires parts of the filesystem to be mapped into a private URL
  23. # hierarachy.
  24. #
  25. # The following example shows the Nginx configuration required to create
  26. # a private "/files/" area, enable X-Accel-Redirect, and pass the special
  27. # X-Sendfile-Type and X-Accel-Mapping headers to the backend:
  28. #
  29. # location ~ /files/(.*) {
  30. # internal;
  31. # alias /var/www/$1;
  32. # }
  33. #
  34. # location / {
  35. # proxy_redirect off;
  36. #
  37. # proxy_set_header Host $host;
  38. # proxy_set_header X-Real-IP $remote_addr;
  39. # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  40. #
  41. # proxy_set_header X-Sendfile-Type X-Accel-Redirect;
  42. # proxy_set_header X-Accel-Mapping /var/www/=/files/;
  43. #
  44. # proxy_pass http://127.0.0.1:8080/;
  45. # }
  46. #
  47. # Note that the X-Sendfile-Type header must be set exactly as shown above.
  48. # The X-Accel-Mapping header should specify the location on the file system,
  49. # followed by an equals sign (=), followed name of the private URL pattern
  50. # that it maps to. The middleware performs a simple substitution on the
  51. # resulting path.
  52. #
  53. # See Also: http://wiki.codemongers.com/NginxXSendfile
  54. #
  55. # === lighttpd
  56. #
  57. # Lighttpd has supported some variation of the X-Sendfile header for some
  58. # time, although only recent version support X-Sendfile in a reverse proxy
  59. # configuration.
  60. #
  61. # $HTTP["host"] == "example.com" {
  62. # proxy-core.protocol = "http"
  63. # proxy-core.balancer = "round-robin"
  64. # proxy-core.backends = (
  65. # "127.0.0.1:8000",
  66. # "127.0.0.1:8001",
  67. # ...
  68. # )
  69. #
  70. # proxy-core.allow-x-sendfile = "enable"
  71. # proxy-core.rewrite-request = (
  72. # "X-Sendfile-Type" => (".*" => "X-Sendfile")
  73. # )
  74. # }
  75. #
  76. # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
  77. #
  78. # === Apache
  79. #
  80. # X-Sendfile is supported under Apache 2.x using a separate module:
  81. #
  82. # https://tn123.org/mod_xsendfile/
  83. #
  84. # Once the module is compiled and installed, you can enable it using
  85. # XSendFile config directive:
  86. #
  87. # RequestHeader Set X-Sendfile-Type X-Sendfile
  88. # ProxyPassReverse / http://localhost:8001/
  89. # XSendFile on
  90. class Sendfile
  91. F = ::File
  92. def initialize(app, variation=nil)
  93. @app = app
  94. @variation = variation
  95. end
  96. def call(env)
  97. status, headers, body = @app.call(env)
  98. if body.respond_to?(:to_path)
  99. case type = variation(env)
  100. when 'X-Accel-Redirect'
  101. path = F.expand_path(body.to_path)
  102. if url = map_accel_path(env, path)
  103. headers['Content-Length'] = '0'
  104. headers[type] = url
  105. body = []
  106. else
  107. env['rack.errors'].puts "X-Accel-Mapping header missing"
  108. end
  109. when 'X-Sendfile', 'X-Lighttpd-Send-File'
  110. path = F.expand_path(body.to_path)
  111. headers['Content-Length'] = '0'
  112. headers[type] = path
  113. body = []
  114. when '', nil
  115. else
  116. env['rack.errors'].puts "Unknown x-sendfile variation: '#{variation}'.\n"
  117. end
  118. end
  119. [status, headers, body]
  120. end
  121. private
  122. def variation(env)
  123. @variation ||
  124. env['sendfile.type'] ||
  125. env['HTTP_X_SENDFILE_TYPE']
  126. end
  127. def map_accel_path(env, file)
  128. if mapping = env['HTTP_X_ACCEL_MAPPING']
  129. internal, external = mapping.split('=', 2).map{ |p| p.strip }
  130. file.sub(/^#{internal}/i, external)
  131. end
  132. end
  133. end
  134. end