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.
Files changed (4) hide show
  1. data/VERSION +1 -1
  2. data/bin/httphere +162 -27
  3. data/httphere.gemspec +3 -3
  4. metadata +24 -13
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.1.0
data/bin/httphere CHANGED
@@ -1,7 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $VERSION = '0.0.1'
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.length > 0)
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.length].sort.first
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
- # If .markdown, render as Markdown
558
- if file_extension == 'markdown'
559
- file_body = Renderers::Markdown.render_content(file_body)
560
- content_type = 'text/html'
561
- end
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
- # If .textile, render as Textile
564
- if file_extension == 'textile'
565
- file_body = Renderers::Textile.render_content(file_body)
566
- content_type = 'text/html'
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, file_body)
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
- # Convert to UTF-8 if possible
582
- chardet = UniversalDetector::chardet(body)
583
- if chardet['confidence'] > 0.7
584
- charset = chardet['encoding']
585
- body = Iconv.conv('utf-8', charset, body)
586
- # else # no conversion
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
- body_length = body.length
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.1"
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-02-10}
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.5}
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
- version: 1.0.1
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-02-10 00:00:00 -05:00
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
- type: :runtime
18
- version_requirement:
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
- version:
30
+ type: :runtime
31
+ version_requirements: *id001
25
32
  - !ruby/object:Gem::Dependency
26
33
  name: chardet
27
- type: :runtime
28
- version_requirement:
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
- version:
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.5
94
+ rubygems_version: 1.3.6
84
95
  signing_key:
85
96
  specification_version: 3
86
97
  summary: A simple Ruby HTTP file server