httphere 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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