httphere 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/bin/httphere +162 -27
- data/httphere.gemspec +3 -3
- metadata +24 -13
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
data/bin/httphere
CHANGED
@@ -1,7 +1,19 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
$VERSION =
|
3
|
+
$VERSION = File.read(File.dirname(__FILE__) + '/../VERSION').chomp
|
4
4
|
$DEBUG = false
|
5
|
+
if $DEBUG
|
6
|
+
require 'benchmark'
|
7
|
+
require 'rubygems'
|
8
|
+
require 'ruby-debug'
|
9
|
+
end
|
10
|
+
def log(*msgs)
|
11
|
+
msgs.each do |msg|
|
12
|
+
$stdout << msg
|
13
|
+
end
|
14
|
+
$stdout.flush
|
15
|
+
true
|
16
|
+
end
|
5
17
|
|
6
18
|
require 'socket'
|
7
19
|
$options = {}
|
@@ -24,6 +36,23 @@ optparse = OptionParser.new do |opts|
|
|
24
36
|
$options[:address] = address
|
25
37
|
end
|
26
38
|
|
39
|
+
$options[:cache_size] = nil
|
40
|
+
opts.on( '--cache-size SIZE', "Turn in-memory caching on and set the maximum cache size. Example: 500K, 10M, 1G") do |size|
|
41
|
+
$options[:cache_size] = case size
|
42
|
+
when /^\d+$/
|
43
|
+
size.to_i
|
44
|
+
when /^\d+kb?$/i
|
45
|
+
size.to_i * 1024 # kilobytes
|
46
|
+
when /^\d+mb?$/i
|
47
|
+
size.to_i * 1024*1024 # megabytes
|
48
|
+
when /^\d+gb?$/i
|
49
|
+
size.to_i * 1024*1024*1000 # gigabytes
|
50
|
+
else
|
51
|
+
warn "Couldn't understand --cache-size #{size}!"
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
27
56
|
$options[:https_domain] = $options[:address]
|
28
57
|
end
|
29
58
|
|
@@ -393,7 +422,7 @@ module EventParsers
|
|
393
422
|
BlankLineRE = /^[\n\r]+$/
|
394
423
|
|
395
424
|
def receive_data(data)
|
396
|
-
return unless (data and data.
|
425
|
+
return unless (data and data.size > 0)
|
397
426
|
|
398
427
|
@last_activity = Time.now
|
399
428
|
|
@@ -448,7 +477,7 @@ module EventParsers
|
|
448
477
|
when :entity
|
449
478
|
if parser.entity_size
|
450
479
|
chars_yet_needed = parser.entity_size - parser.entity_pos
|
451
|
-
taking_this_many = [chars_yet_needed, data.
|
480
|
+
taking_this_many = [chars_yet_needed, data.size].sort.first
|
452
481
|
parser.textbuffer << data[0...taking_this_many]
|
453
482
|
leftover_data = data[taking_this_many..-1]
|
454
483
|
parser.entity_pos += taking_this_many
|
@@ -540,34 +569,89 @@ module Renderers
|
|
540
569
|
end
|
541
570
|
require 'UniversalDetector'
|
542
571
|
require 'shared-mime-info'
|
572
|
+
class File
|
573
|
+
def size
|
574
|
+
File.size(path)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
543
578
|
class FileServer < EventParsers::Http11Parser::Request
|
579
|
+
class << self
|
580
|
+
attr_accessor :cache
|
581
|
+
def cached?(filename)
|
582
|
+
# Serve from cache if it's in the cache and if the file hasn't changed.
|
583
|
+
if cache
|
584
|
+
# Delete from cache if the file's been modified since last cache.
|
585
|
+
cache.delete(filename) if cache.has_key?(filename) && File.mtime(filename) > cache[filename][0][:mtime]
|
586
|
+
cache.has_key?(filename)
|
587
|
+
end
|
588
|
+
end
|
589
|
+
def from_cache(filename)
|
590
|
+
cache[filename][0][:last_accessed_at] = Time.now
|
591
|
+
puts "From cache: #{filename} / #{cache[filename][0][:size]}"
|
592
|
+
[cache[filename][1], cache[filename][0][:content_type]]
|
593
|
+
end
|
594
|
+
def cache?(filename,response_body)
|
595
|
+
# Obviously, don't cache if it's bigger than the cache max size
|
596
|
+
cache && File.exists?(filename) && response_body.size < cache.max_size
|
597
|
+
end
|
598
|
+
def cache!(filename,content_type,response_body)
|
599
|
+
if cache?(filename,response_body)
|
600
|
+
puts "Caching #{filename} / #{response_body.size}"
|
601
|
+
cache[filename] = [
|
602
|
+
{
|
603
|
+
:last_accessed_at => Time.now,
|
604
|
+
:mtime => File.mtime(filename),
|
605
|
+
:size => response_body.size,
|
606
|
+
:content_type => content_type
|
607
|
+
},
|
608
|
+
response_body
|
609
|
+
]
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
544
614
|
# This is where the routing is processed.
|
545
615
|
def process
|
546
616
|
# Get the filename desired
|
547
617
|
filename, query = resource_uri.split('?',2)
|
548
|
-
filename = filename.sub(/^\//,'')
|
618
|
+
@filename = filename.sub(/^\//,'')
|
549
619
|
# Default to any file named index.*
|
550
|
-
filename = Dir["index.*"].first if filename.to_s == '' && Dir["index.*"].length > 0
|
620
|
+
@filename = Dir["index.*"].first if filename.to_s == '' && Dir["index.*"].length > 0
|
551
621
|
file_extension = (filename.match(/\.([^\.]+)$/) || [])[1]
|
552
622
|
|
553
|
-
if File.exists?(filename) && !File.directory?(filename)
|
554
|
-
content_type = MIME.check(filename).type
|
555
|
-
file_body = File.read(filename)
|
623
|
+
if File.exists?(@filename) && !File.directory?(@filename)
|
556
624
|
|
557
|
-
#
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
625
|
+
# Read from cache if possible
|
626
|
+
file_handle = FileServer.cached?(@filename) ? :from_cache : :from_disk
|
627
|
+
if file_handle == :from_disk
|
628
|
+
if $DEBUG
|
629
|
+
Benchmark.bm do |x|
|
630
|
+
x.report("Getting MIME type") do
|
631
|
+
@content_type = MIME.check(@filename).type
|
632
|
+
end
|
633
|
+
end
|
634
|
+
else
|
635
|
+
@content_type = MIME.check(@filename).type
|
636
|
+
end
|
562
637
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
638
|
+
file_handle = File.open(@filename)
|
639
|
+
|
640
|
+
# If .markdown, render as Markdown
|
641
|
+
if file_extension == 'markdown'
|
642
|
+
file_handle = Renderers::Markdown.render_content(file_handle.read)
|
643
|
+
@content_type = 'text/html'
|
644
|
+
end
|
645
|
+
|
646
|
+
# If .textile, render as Textile
|
647
|
+
if file_extension == 'textile'
|
648
|
+
file_handle = Renderers::Textile.render_content(file_handle.read)
|
649
|
+
@content_type = 'text/html'
|
650
|
+
end
|
567
651
|
end
|
568
652
|
|
569
653
|
# Send Response
|
570
|
-
respond!('200 Ok', content_type,
|
654
|
+
respond!('200 Ok', @content_type, file_handle)
|
571
655
|
else
|
572
656
|
respond!('404 Not Found', 'text/plain', "Could not find file: '#{resource_uri}'")
|
573
657
|
end
|
@@ -578,19 +662,39 @@ class FileServer < EventParsers::Http11Parser::Request
|
|
578
662
|
connection.halt!
|
579
663
|
end
|
580
664
|
def respond(status, content_type, body)
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
665
|
+
if body == :from_cache
|
666
|
+
body, content_type = FileServer.from_cache(@filename)
|
667
|
+
else
|
668
|
+
body = StringIO.new(body.to_s) if !body.is_a?(IO) && body.respond_to?(:to_s)
|
669
|
+
|
670
|
+
# Convert to UTF-8 if possible
|
671
|
+
chardet = UniversalDetector::chardet(body.read(512)); body.rewind # Detect charset only from the first 512 bytes
|
672
|
+
if chardet['confidence'] > 0.7 && ['utf-8', 'ascii'].include?(chardet['encoding'])
|
673
|
+
if $DEBUG
|
674
|
+
Benchmark.bm do |x|
|
675
|
+
x.report("Converting from #{chardet['encoding']} to UTF-8") do
|
676
|
+
charset = chardet['encoding']
|
677
|
+
body = StringIO.new(Iconv.conv('utf-8', charset, body.read))
|
678
|
+
end
|
679
|
+
end
|
680
|
+
else
|
681
|
+
charset = chardet['encoding']
|
682
|
+
body = StringIO.new(Iconv.conv('utf-8', charset, body.read))
|
683
|
+
end
|
684
|
+
else # no conversion
|
685
|
+
puts "No charset conversion necessary." if $DEBUG
|
686
|
+
end
|
687
|
+
|
688
|
+
# Write to cache if we should
|
689
|
+
FileServer.cache!(@filename, content_type, body)
|
587
690
|
end
|
588
|
-
|
691
|
+
|
692
|
+
body_length = body.size
|
693
|
+
body.rewind
|
589
694
|
|
590
695
|
# Send the response!
|
591
|
-
connection.send_response! "HTTP/1.1 #{status}\r\nServer: HTTP-Here, version #{$VERSION}\r\nContent-Type: #{content_type}\r\nContent-Length: #{body_length+2}\r\n\r\n#{body}\r\n"
|
696
|
+
connection.send_response! "HTTP/1.1 #{status}\r\nServer: HTTP-Here, version #{$VERSION}\r\nContent-Type: #{content_type}\r\nContent-Length: #{body_length+2}\r\n\r\n#{body.read}\r\n"
|
592
697
|
span = (Time.now - connection.time).to_f
|
593
|
-
content_type
|
594
698
|
puts (status =~ /200/ ?
|
595
699
|
"Served #{resource_uri} (#{content_type})" :
|
596
700
|
"404 #{resource_uri}"
|
@@ -632,6 +736,37 @@ end
|
|
632
736
|
|
633
737
|
# EventMachineMini.ssl_config[:GenerateSSLCert] = true
|
634
738
|
|
739
|
+
class Cache
|
740
|
+
def initialize(config={})
|
741
|
+
@config = config
|
742
|
+
@cache = {}
|
743
|
+
end
|
744
|
+
def max_size
|
745
|
+
@config[:max_size]
|
746
|
+
end
|
747
|
+
def [](key)
|
748
|
+
@cache[key]
|
749
|
+
end
|
750
|
+
def []=(key,value)
|
751
|
+
@cache[key] = value
|
752
|
+
end
|
753
|
+
def has_key?(key)
|
754
|
+
@cache.has_key?(key)
|
755
|
+
end
|
756
|
+
def delete(key)
|
757
|
+
@cache.delete(key)
|
758
|
+
end
|
759
|
+
def bytes
|
760
|
+
@cache.values.inject(0) {|s,v| s+v[0][:size]}
|
761
|
+
end
|
762
|
+
end
|
763
|
+
|
764
|
+
# Turn Caching on if asked for
|
765
|
+
if $options[:cache_size]
|
766
|
+
FileServer.cache = Cache.new(:max_size => $options[:cache_size])
|
767
|
+
puts "Caching files in memory, using up to #{$options[:cache_size]} bytes."
|
768
|
+
end
|
769
|
+
|
635
770
|
puts "HTTP Here v#{$VERSION} : listening on #{$options[:address]}:#{$options[:port]}..."
|
636
771
|
begin
|
637
772
|
$server = EventMachineMini.new( :listen => {"#{$options[:address]}:#{$options[:port]}" => Http11Server} )
|
data/httphere.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{httphere}
|
8
|
-
s.version = "1.0
|
8
|
+
s.version = "1.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Daniel Parker"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-03-12}
|
13
13
|
s.default_executable = %q{httphere}
|
14
14
|
s.description = %q{httphere is a very small and simple ruby command-line HTTP file server.}
|
15
15
|
s.email = %q{gems@behindlogic.com}
|
@@ -42,7 +42,7 @@ Assuming you're on a Mac, you can simply run:
|
|
42
42
|
}
|
43
43
|
s.rdoc_options = ["--charset=UTF-8"]
|
44
44
|
s.require_paths = ["lib"]
|
45
|
-
s.rubygems_version = %q{1.3.
|
45
|
+
s.rubygems_version = %q{1.3.6}
|
46
46
|
s.summary = %q{A simple Ruby HTTP file server}
|
47
47
|
|
48
48
|
if s.respond_to? :specification_version then
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: httphere
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 1.1.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Daniel Parker
|
@@ -9,29 +14,33 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-03-12 00:00:00 -05:00
|
13
18
|
default_executable: httphere
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: shared-mime-info
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
25
|
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
23
29
|
version: "0"
|
24
|
-
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
25
32
|
- !ruby/object:Gem::Dependency
|
26
33
|
name: chardet
|
27
|
-
|
28
|
-
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
37
|
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 0
|
33
41
|
version: "0"
|
34
|
-
|
42
|
+
type: :runtime
|
43
|
+
version_requirements: *id002
|
35
44
|
description: httphere is a very small and simple ruby command-line HTTP file server.
|
36
45
|
email: gems@behindlogic.com
|
37
46
|
executables:
|
@@ -69,18 +78,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
78
|
requirements:
|
70
79
|
- - ">="
|
71
80
|
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
72
83
|
version: "0"
|
73
|
-
version:
|
74
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
85
|
requirements:
|
76
86
|
- - ">="
|
77
87
|
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
78
90
|
version: "0"
|
79
|
-
version:
|
80
91
|
requirements: []
|
81
92
|
|
82
93
|
rubyforge_project:
|
83
|
-
rubygems_version: 1.3.
|
94
|
+
rubygems_version: 1.3.6
|
84
95
|
signing_key:
|
85
96
|
specification_version: 3
|
86
97
|
summary: A simple Ruby HTTP file server
|