httphere 1.0.1 → 1.1.0
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.
- 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
|