websocket-server 1.0.1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +185 -0
  4. data/Rakefile +56 -0
  5. data/exe/websocket +41 -0
  6. data/lib/log.rb +244 -0
  7. data/lib/server/mime_types.rb +38 -0
  8. data/lib/websocket/arguments_parser.rb +142 -0
  9. data/lib/websocket/channel_initializer.rb +73 -0
  10. data/lib/websocket/config.rb +59 -0
  11. data/lib/websocket/encoding.rb +21 -0
  12. data/lib/websocket/file_server_channel_progressive_future_listener.rb +32 -0
  13. data/lib/websocket/frame_handler.rb +71 -0
  14. data/lib/websocket/header_helpers.rb +70 -0
  15. data/lib/websocket/http_static_file_server_handler.rb +50 -0
  16. data/lib/websocket/http_static_file_server_handler_instance_methods.rb +160 -0
  17. data/lib/websocket/idle_handler.rb +41 -0
  18. data/lib/websocket/idle_state_user_event_handler.rb +47 -0
  19. data/lib/websocket/instance_methods.rb +127 -0
  20. data/lib/websocket/listenable.rb +41 -0
  21. data/lib/websocket/message_handler.rb +47 -0
  22. data/lib/websocket/response_helpers.rb +83 -0
  23. data/lib/websocket/server.rb +26 -0
  24. data/lib/websocket/shutdown_hook.rb +36 -0
  25. data/lib/websocket/ssl_cipher_inspector.rb +44 -0
  26. data/lib/websocket/ssl_context_initialization.rb +106 -0
  27. data/lib/websocket/telnet_proxy.rb +22 -0
  28. data/lib/websocket/validation_helpers.rb +51 -0
  29. data/lib/websocket/version.rb +16 -0
  30. data/lib/websocket-server.rb +13 -0
  31. data/lib/websocket_client.rb +478 -0
  32. data/lib/websocket_server.rb +50 -0
  33. data/web/client.html +43 -0
  34. data/web/css/client/console.css +167 -0
  35. data/web/css/client/parchment.css +112 -0
  36. data/web/favicon.ico +0 -0
  37. data/web/fonts/droidsansmono.v4.woff +0 -0
  38. data/web/js/client/ansispan.js +103 -0
  39. data/web/js/client/client.js +144 -0
  40. data/web/js/client/console.js +393 -0
  41. data/web/js/client/websocket.js +76 -0
  42. data/web/js/jquery.min.js +2 -0
  43. metadata +145 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7670d3a4e743e533e42467d4047741a0ea833cc4e7ea4d1855a1ed9b76138560
4
+ data.tar.gz: d1d6b8efb9502a45b44d56267d8a76fc036d46e4169c15cf93fcc605202ded8c
5
+ SHA512:
6
+ metadata.gz: a7cf7669311ab73b6a1905cda6d8112052b32ddbd7da3ccfb6bcf36e36e3f1d66b8fabf11da1b0abe0dc29a61c96f10506c1a8c750f95a45a18f494e711ec24f
7
+ data.tar.gz: 16e0b9f2221cd9a83de57266561ea8797f40ac7d6d717e6b67fef1e078f045a5b2a5605c25c5670bc6fdde6c5db48610b85786f0a5c1a79d2a7df20c366ef6d6
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Nels Nelson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # websocket-server-jruby
2
+
3
+ [![License](https://img.shields.io/badge/license-MIT--2.0-blue.svg?style=flat)][license]
4
+
5
+ This is a small websocket server for [JRuby].
6
+
7
+ It is based on the [Netty project]. Netty is written in java, but I wanted to write ruby.
8
+
9
+
10
+ ## Quick-start
11
+
12
+ Follow these instructions to get a websocket server echo program running in your web browser.
13
+
14
+
15
+ ### Container
16
+
17
+ You may run the websocket server in a container.
18
+
19
+ ```sh
20
+ colima start
21
+ docker-compose up &
22
+ open http://localhost:4000/client.html
23
+ ```
24
+
25
+
26
+ Building the image or running the container:
27
+
28
+ ```sh
29
+ docker build --squash --tag websocket-server-jruby .
30
+ docker run --detach --publish 4000:4000 --name websocket-server-jruby websocket-server-jruby
31
+ ```
32
+
33
+
34
+ ## Manually
35
+
36
+ Run directly with the required dependencies installed.
37
+
38
+ ### Install asdf
39
+
40
+ The [asdf] CLI tool used to manage multiple runtime versions.
41
+
42
+ ```sh
43
+ git clone https://github.com/asdf-vm/asdf.git "${HOME}/.asdf" --branch v0.8.1
44
+ pushd "${HOME}/.asdf"; git fetch origin; popd
45
+ source "${HOME}/.asdf/asdf.sh"; source "${HOME}/.asdf/completions/asdf.bash"
46
+ ```
47
+
48
+ ### Install required runtime software
49
+
50
+ Download and install the latest version of the [Java JDK].
51
+
52
+ ```sh
53
+ asdf plugin add java
54
+ asdf install java openjdk-17.0.2
55
+ ```
56
+
57
+
58
+ Download and install the latest version of [JRuby].
59
+
60
+ ```sh
61
+ asdf plugin add ruby
62
+ pushd "${HOME}/.asdf/plugins/ruby/ruby-build"; git fetch origin; git pull origin master; popd
63
+ # ~/.asdf/plugins/ruby/bin/list-all
64
+ asdf list all ruby
65
+ asdf install
66
+ ```
67
+
68
+
69
+ Install the project dependencies.
70
+
71
+ ```sh
72
+ bundle install
73
+ ```
74
+
75
+
76
+ ## Run
77
+
78
+ The entrypoint for the web application service may now be invoked from a command line interface terminal shell.
79
+
80
+ ```sh
81
+ bundle exec ./websocket.rb &
82
+ open http://localhost:4000/client.html
83
+ ```
84
+
85
+
86
+ ## Build the gem
87
+
88
+ To clean the project, run unit tests, build the gem file, and verify that the built artifact works, execute:
89
+
90
+ ```sh
91
+ bundle exec rake
92
+ ```
93
+
94
+
95
+ ## Publish the gem
96
+
97
+ To publish the gem, execute:
98
+
99
+ ```sh
100
+ bundle exec rake publish
101
+ ```
102
+
103
+
104
+ ## Project file tree
105
+
106
+ Here is a bird's-eye view of the project layout.
107
+
108
+ ```sh
109
+ # date && tree
110
+ Wed Apr 6 23:05:28 CDT 2022
111
+ .
112
+ ├── Dockerfile
113
+ ├── Gemfile
114
+ ├── Gemfile.lock
115
+ ├── LICENSE
116
+ ├── README.md
117
+ ├── Rakefile
118
+ ├── docker-compose.yaml
119
+ ├── exe
120
+ │ └── websocket
121
+ ├── lib
122
+ │ ├── log.rb
123
+ │ ├── server
124
+ │ │ ├── mime_types.rb
125
+ │ │ └── server.rb
126
+ │ ├── websocket
127
+ │ │ ├── arguments_parser.rb
128
+ │ │ ├── channel_initializer.rb
129
+ │ │ ├── config.rb
130
+ │ │ ├── encoding.rb
131
+ │ │ ├── file_server_channel_progressive_future_listener.rb
132
+ │ │ ├── frame_handler.rb
133
+ │ │ ├── header_helpers.rb
134
+ │ │ ├── http_static_file_server_handler.rb
135
+ │ │ ├── http_static_file_server_handler_instance_methods.rb
136
+ │ │ ├── idle_handler.rb
137
+ │ │ ├── idle_state_user_event_handler.rb
138
+ │ │ ├── listenable.rb
139
+ │ │ ├── message_handler.rb
140
+ │ │ ├── modular_handler.rb
141
+ │ │ ├── response_helpers.rb
142
+ │ │ ├── server.rb
143
+ │ │ ├── server_instance_methods.rb
144
+ │ │ ├── shutdown_hook.rb
145
+ │ │ ├── ssl_cipher_inspector.rb
146
+ │ │ ├── ssl_context_initialization.rb
147
+ │ │ ├── telnet_proxy.rb
148
+ │ │ ├── validation_helpers.rb
149
+ │ │ └── version.rb
150
+ │ ├── websocket_client.rb
151
+ │ └── websocket_server.rb
152
+ ├── logs
153
+ │ └── server.log
154
+ ├── spec
155
+ │ ├── spec_helper.rb
156
+ │ ├── test_spec.rb
157
+ │ └── verify
158
+ │ └── verify_spec.rb
159
+ ├── web
160
+ │ ├── client.html
161
+ │ ├── css
162
+ │ │ └── client
163
+ │ │ ├── console.css
164
+ │ │ └── parchment.css
165
+ │ ├── favicon.ico
166
+ │ ├── fonts
167
+ │ │ └── droidsansmono.v4.woff
168
+ │ └── js
169
+ │ ├── client
170
+ │ │ ├── ansispan.js
171
+ │ │ ├── client.js
172
+ │ │ ├── console.js
173
+ │ │ └── websocket.js
174
+ │ └── jquery.min.js
175
+ ├── websocket-server-jruby.gemspec
176
+ └── websocket.rb
177
+
178
+ 13 directories, 52 files
179
+ ```
180
+
181
+ [license]: https://gitlab.com/nelsnelson/websocket-server-jruby/blob/master/LICENSE
182
+ [asdf]: https://asdf-vm.com/
183
+ [Netty project]: https://github.com/netty/netty
184
+ [Java JDK]: https://www.java.com/en/download/
185
+ [JRuby]: https://jruby.org/download
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ require 'rake'
8
+ require 'rake/clean'
9
+
10
+ PROJECT = File.basename(__dir__) unless defined?(PROJECT)
11
+
12
+ load "#{PROJECT}.gemspec"
13
+
14
+ CLEAN.add File.join('tmp', '**', '*'), 'tmp'
15
+ CLOBBER.add '*.gem', 'pkg'
16
+
17
+ task default: %i[package]
18
+
19
+ desc 'Run the rubocop linter'
20
+ task :lint do
21
+ system('bundle', 'exec', 'rubocop') or abort
22
+ end
23
+
24
+ desc 'Run the spec tests'
25
+ task :test do
26
+ system('bundle', 'exec', 'rspec', '--exclude-pattern', 'spec/verify/**/*_spec.rb') or abort
27
+ end
28
+ task test: :lint
29
+
30
+ desc 'Explode the gem'
31
+ task :explode do
32
+ system('jgem', 'install', '--no-document', '--install-dir=tmp', '*.gem')
33
+ end
34
+ task explode: :clean
35
+
36
+ desc 'Package the gem'
37
+ task :package do
38
+ system('jgem', 'build')
39
+ end
40
+ task package: %i[clean clobber test]
41
+
42
+ desc 'Verify the gem'
43
+ task :verify do
44
+ system('bundle', 'exec', 'rspec', 'spec/verify') or abort
45
+ end
46
+ task verify: :explode
47
+
48
+ desc 'Publish the gem'
49
+ task :publish do
50
+ system('jgem', 'push', latest_gem)
51
+ end
52
+ task publish: :verify
53
+
54
+ def latest_gem
55
+ `ls -t #{PROJECT}*.gem`.strip.split("\n").first
56
+ end
data/exe/websocket ADDED
@@ -0,0 +1,41 @@
1
+ #! /usr/bin/env jruby
2
+
3
+ # encoding: utf-8
4
+ # frozen_string_literal: false
5
+
6
+ # -*- mode: ruby -*-
7
+ # vi: set ft=ruby :
8
+
9
+ # =begin
10
+ #
11
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
12
+ #
13
+ # =end
14
+
15
+ # How to invoke this program with Java system property 'ssl' set to true
16
+ # without root privileges:
17
+ #
18
+ # websocket --ssl --port=4443
19
+ #
20
+ # You may now browse to https://localhost:4443/ to test.
21
+ #
22
+ #
23
+ # How to have this program listen to standard ssl port 443 (this
24
+ # typically requires root privileges):
25
+ #
26
+ # sudo websocket --ssl
27
+ #
28
+ #
29
+ # How to have this program listen to standard ssl port 443 and use
30
+ # a certificate and private key from the local file system:
31
+ #
32
+ # sudo websocket --ssl \
33
+ # --ssl-certificate=/etc/letsencrypt/live/example.com/fullchain.pem \
34
+ # --ssl-private-key=/etc/letsencrypt/live/example.com/privkey.pem
35
+ #
36
+ # You may now browse to https://localhost/ to test.
37
+ #
38
+
39
+ require_relative '../lib/websocket_server'
40
+
41
+ Object.new.extend(WebSocket).main
data/lib/log.rb ADDED
@@ -0,0 +1,244 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # =begin
5
+
6
+ # Copyright Nels Nelson 2016-2019 but freely usable (see license)
7
+
8
+ # =end
9
+
10
+ require 'java'
11
+ require 'logger'
12
+
13
+ require 'log4j-2'
14
+
15
+ require 'fileutils'
16
+
17
+ # The Logging module
18
+ module Logging
19
+ # rubocop: disable Metrics/MethodLength
20
+ def config
21
+ @config ||= begin
22
+ lib_dir_path = File.expand_path(__dir__)
23
+ project_dir_path = File.expand_path(File.dirname(lib_dir_path))
24
+ logs_dir_path = File.expand_path(File.join(project_dir_path, 'logs'))
25
+ server_log_file = File.expand_path(File.join(logs_dir_path, 'server.log'))
26
+ {
27
+ level: :info,
28
+ name: 'websocket',
29
+ lib_dir_path: lib_dir_path,
30
+ project_dir_path: project_dir_path,
31
+ logs_dir_path: logs_dir_path,
32
+ server_log_file: server_log_file,
33
+ rolling_log_file_name_template: 'server-%d{yyyy-MM-dd}.log.gz',
34
+ logger_pattern_template: '%d{ABSOLUTE} %-5p [%c{1}] %m%n',
35
+ schedule: '0 0 0 * * ?',
36
+ size: '100M'
37
+ }
38
+ end
39
+ end
40
+ module_function :config
41
+ # rubocop: enable Metrics/MethodLength
42
+ end
43
+
44
+ # The LogInitialization module
45
+ module LogInitialization
46
+ def init
47
+ init_log_file
48
+ init_log4j if defined? Java
49
+ end
50
+ module_function :init
51
+
52
+ def init_log_file
53
+ FileUtils.mkdir_p(Logging.config[:logs_dir_path])
54
+ return if File.file?(Logging.config[:server_log_file])
55
+
56
+ File.write(Logging.config[:server_log_file], '')
57
+ end
58
+ module_function :init_log_file
59
+
60
+ # rubocop: disable Metrics/AbcSize
61
+ # rubocop: disable Metrics/MethodLength
62
+ def init_log4j(log_level = org.apache.logging.log4j.Level::INFO)
63
+ server_log_file = Logging.config[:server_log_file]
64
+ logs_dir_path = Logging.config[:logs_dir_path]
65
+ rolling_log_file_name_template = Logging.config[:rolling_log_file_name_template]
66
+ rolling_log_file_path = File.join(logs_dir_path, rolling_log_file_name_template)
67
+
68
+ java.lang::System.setProperty('log4j.shutdownHookEnabled', java.lang::Boolean.toString(false))
69
+ factory = org.apache.logging.log4j.core.config.builder.api::ConfigurationBuilderFactory
70
+ config = factory.newConfigurationBuilder()
71
+
72
+ if log_level.is_a?(Symbol)
73
+ log_level = org.apache.logging.log4j.Level.to_level(
74
+ log_level.to_s.upcase
75
+ )
76
+ end
77
+ config.setStatusLevel(log_level)
78
+ config.setConfigurationName(Logging.config['name'])
79
+
80
+ # Create a console appender
81
+ target = org.apache.logging.log4j.core.appender::ConsoleAppender::Target::SYSTEM_OUT
82
+ layout = config.newLayout('PatternLayout')
83
+ layout = layout.addAttribute('pattern', Logging.config[:logger_pattern_template])
84
+ appender = config.newAppender('stdout', 'CONSOLE')
85
+ appender = appender.addAttribute('target', target)
86
+ appender = appender.add(layout)
87
+ config.add(appender)
88
+
89
+ # Create a root logger
90
+ root_logger = config.newRootLogger(log_level)
91
+ root_logger = root_logger.add(config.newAppenderRef('stdout'))
92
+
93
+ # Create a rolling file appender
94
+ cron = config.newComponent('CronTriggeringPolicy')
95
+ cron = cron.addAttribute('schedule', Logging.config[:schedule])
96
+
97
+ size = config.newComponent('SizeBasedTriggeringPolicy')
98
+ size = size.addAttribute('size', Logging.config[:size])
99
+
100
+ policies = config.newComponent('Policies')
101
+ policies = policies.addComponent(cron)
102
+ policies = policies.addComponent(size)
103
+
104
+ appender = config.newAppender('rolling_file', 'RollingFile')
105
+ appender = appender.addAttribute('fileName', server_log_file)
106
+ appender = appender.addAttribute('filePattern', rolling_log_file_path)
107
+ appender = appender.add(layout)
108
+ appender = appender.addComponent(policies)
109
+ config.add(appender)
110
+
111
+ root_logger = root_logger.addAttribute('additivity', false)
112
+ root_logger = root_logger.add(config.newAppenderRef('rolling_file'))
113
+ config.add(root_logger)
114
+
115
+ logging_configuration = config.build()
116
+ ctx = org.apache.logging.log4j.core.config::Configurator.initialize(logging_configuration)
117
+ ctx.updateLoggers()
118
+ end
119
+ # rubocop: enable Metrics/AbcSize
120
+ # rubocop: enable Metrics/MethodLength
121
+ # def init_log4j
122
+ module_function :init_log4j
123
+ end
124
+ # module LogInitialization
125
+
126
+ ::LogInitialization.init
127
+
128
+ # The Apache log4j Logger class
129
+ # rubocop: disable Style/ClassAndModuleChildren
130
+ class org.apache.logging.log4j.core::Logger
131
+ alias log4j_error error
132
+ def error(error_or_message, error = nil)
133
+ return extract_backtrace(error_or_message) if error.nil?
134
+ log4j_error(generate_message(error_or_message, error))
135
+ extract_backtrace(error)
136
+ end
137
+
138
+ def generate_message(error_or_message, error)
139
+ error_message = "#{error_or_message}: #{error.class.name}"
140
+ error_message << ": #{error.message}" if error.respond_to?(:message)
141
+ error_message
142
+ end
143
+
144
+ def extract_backtrace(error, default_result = nil)
145
+ log4j_error(error)
146
+ if error.respond_to?(:backtrace)
147
+ error.backtrace.each { |trace| log4j_error(trace) unless trace.nil? }
148
+ elsif error.respond_to?(:getStackTrace)
149
+ error.getStackTrace().each { |trace| log4j_error(trace) unless trace.nil? }
150
+ else
151
+ default_result
152
+ end
153
+ end
154
+ end
155
+ # rubocop: enable Style/ClassAndModuleChildren
156
+
157
+ # The Logging module
158
+ module Logging
159
+ def init_logger(level = :all, logger_name = nil)
160
+ return init_java_logger(level, logger_name, caller[2]) if defined?(Java)
161
+ init_ruby_logger(level)
162
+ end
163
+
164
+ def init_ruby_logger(level)
165
+ logger_instance = Logger.new
166
+ logger_instance.level = Logging::Level.to_level(level.to_s.upcase)
167
+ logger_instance
168
+ end
169
+
170
+ # rubocop: disable Metrics/AbcSize
171
+ def init_java_logger(level, logger_name = nil, source_location = nil)
172
+ logger_name = get_formatted_logger_name(logger_name)
173
+ logger_name = source_location.split(/\//).last if logger_name.empty?
174
+ logger_instance = org.apache.logging.log4j.LogManager.getLogger(logger_name)
175
+ logger_instance.level = org.apache.logging.log4j.Level.to_level(level.to_s.upcase)
176
+ logger_instance
177
+ end
178
+ # rubocop: enable Metrics/AbcSize
179
+
180
+ def get_formatted_logger_name(logger_name = nil)
181
+ return logger_name.to_s[/\w+$/] unless logger_name.nil?
182
+ return name[/\w+$/] if is_a?(Class) || is_a?(Module)
183
+ self.class.name[/\w+$/]
184
+ end
185
+
186
+ # rubocop: disable Metrics/CyclomaticComplexity
187
+ # OFF: 0
188
+ # FATAL: 100
189
+ # ERROR: 200
190
+ # WARN: 300
191
+ # INFO: 400
192
+ # DEBUG: 500
193
+ # TRACE: 600
194
+ # ALL: 2147483647
195
+ # See: https://logging.apache.org/log4j/2.x/log4j-api/apidocs/org/apache/logging/log4j/Level.html
196
+ def symbolize_numeric_log_level(level)
197
+ case level
198
+ when 5..Float::INFINITY then :off
199
+ when 4 then :fatal
200
+ when 3 then :error
201
+ when 2 then :warn
202
+ when 1 then :info
203
+ when 0 then :debug
204
+ when -1 then :trace
205
+ when -2..-Float::INFINITY then :all
206
+ end
207
+ end
208
+ # rubocop: enable Metrics/CyclomaticComplexity
209
+ module_function :symbolize_numeric_log_level
210
+
211
+ def log_level=(log_level)
212
+ Logging.config[:level] = symbolize_numeric_log_level(log_level)
213
+ end
214
+ module_function :log_level=
215
+
216
+ def log_level
217
+ Logging.config[:level]
218
+ end
219
+ module_function :log_level
220
+
221
+ def log(level = Logging.log_level, log_name = nil)
222
+ @log ||= init_logger(level, log_name)
223
+ end
224
+ alias logger log
225
+ end
226
+ # module Logging
227
+
228
+ # The Module class
229
+ class Module
230
+ # Universally include Logging
231
+ include ::Logging
232
+ end
233
+
234
+ # The Class class
235
+ class Class
236
+ # Universally include Logging
237
+ include ::Logging
238
+ end
239
+
240
+ # The Object class
241
+ class Object
242
+ # Universally include Logging
243
+ include ::Logging
244
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ # The Server module
14
+ module Server
15
+ MimeTypes = {
16
+ txt: 'text/plain',
17
+ css: 'text/css',
18
+ csv: 'text/csv',
19
+ htm: 'text/html',
20
+ html: 'text/html',
21
+ xml: 'text/xml',
22
+ js: 'application/javascript',
23
+ xhtml: 'application/xhtml+xml',
24
+ json: 'application/json',
25
+ pdf: 'application/pdf',
26
+ woff: 'application/x-font-woff',
27
+ zip: 'application/zip',
28
+ tar: 'application/x-tar',
29
+ gif: 'image/gif',
30
+ jpeg: 'image/jpeg',
31
+ jpg: 'image/jpeg',
32
+ tiff: 'image/tiff',
33
+ tif: 'image/tiff',
34
+ png: 'image/png',
35
+ svg: 'image/svg+xml',
36
+ ico: 'image/vnd.microsoft.icon'
37
+ }.freeze
38
+ end