mayfly 0.0.1
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/bin/mayfly +68 -0
- data/lib/mayfly.rb +96 -0
- data/lib/mayfly/file.rb +19 -0
- data/lib/mayfly/servlet.rb +69 -0
- data/lib/mayfly/utils.rb +51 -0
- metadata +77 -0
data/bin/mayfly
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$:.push(File.dirname(__FILE__) + "/../lib")
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'mayfly'
|
8
|
+
|
9
|
+
include Mayfly
|
10
|
+
|
11
|
+
options = OpenStruct.new
|
12
|
+
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
|
15
|
+
opts.banner = 'Usage: mayfly [options] file'
|
16
|
+
opts.on("-h", "--help", "Show this message") {|h| options.help = h}
|
17
|
+
opts.on("-v", "--verbose", "Run verbosely") {|v| options.verbose = v}
|
18
|
+
opts.on("-pnumber", "--port number", Integer, "Port number, default 7887") {|p| options.port = p}
|
19
|
+
opts.on("-lnumber", "--lives number", Integer, "How long mayfly should live for, how many requests it serves, default 1") {|l| options.lives = l}
|
20
|
+
opts.on("--[no-]secure", "Turn https on or off, off by default") {|s| options.secure = s}
|
21
|
+
opts.on("--[no-]auth", "Turn authentication on or off, off by default") {|a| options.auth = a}
|
22
|
+
opts.on("--passwd password", String,"If using auth then provide your password as an argument") {|p| options.passwd = p}
|
23
|
+
opts.on("--version", "Show version") do
|
24
|
+
puts "Mayfly " + Mayfly::VERSION.join('.')
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
opts.parse!
|
30
|
+
rescue OptionParser::MissingArgument,
|
31
|
+
OptionParser::InvalidOption,
|
32
|
+
OptionParser::InvalidArgument => e
|
33
|
+
puts e.message + ', use --help for more details'
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
if (options.help)
|
38
|
+
puts opts
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
if (ARGV.length != 1)
|
43
|
+
puts "File argument not specified, use --help for more details"
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
file = ARGV[0]
|
50
|
+
port = options.port || 7887
|
51
|
+
lives = options.lives || 1
|
52
|
+
secure = options.secure
|
53
|
+
passwd = options.passwd ||= Mayfly::Utils::Cli.get_passwd if (options.auth)
|
54
|
+
verbose = options.verbose || false
|
55
|
+
|
56
|
+
args = [file, port.to_i, lives.to_i, secure, passwd, verbose]
|
57
|
+
|
58
|
+
if (ENV['MAYFLY_TESTING']) # Just test the stuff here happens ok
|
59
|
+
system("echo #{args.join(',')}")
|
60
|
+
else
|
61
|
+
begin
|
62
|
+
server = Server.new(*args)
|
63
|
+
server.start
|
64
|
+
rescue FileNotFoundException => e
|
65
|
+
puts "#{e.message}, shutting down"
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
end
|
data/lib/mayfly.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'webrick/https'
|
3
|
+
require 'logger'
|
4
|
+
require 'mayfly/file'
|
5
|
+
require 'mayfly/servlet'
|
6
|
+
require 'mayfly/utils'
|
7
|
+
include Mayfly::Utils
|
8
|
+
|
9
|
+
module Mayfly
|
10
|
+
|
11
|
+
VERSION = [0,0,1]
|
12
|
+
|
13
|
+
class FileNotFoundException < RuntimeError; end
|
14
|
+
|
15
|
+
class Log < Logger
|
16
|
+
def mayfly_log(msg)
|
17
|
+
(@logdev.nil?) ? puts(msg) : info(msg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Server
|
22
|
+
|
23
|
+
attr_accessor :config
|
24
|
+
|
25
|
+
def initialize(file, port, lives, secure = nil, passwd = nil, verbose = false)
|
26
|
+
|
27
|
+
local_variables.each {|v| eval("@#{v}=eval(v)")}
|
28
|
+
|
29
|
+
if (!File.exists?(@file))
|
30
|
+
raise FileNotFoundException, "#{@file} does not exist"
|
31
|
+
end
|
32
|
+
|
33
|
+
logger.mayfly_log("#{file} will be served #{lives} times from: " +
|
34
|
+
"http#{(secure) ? 's' : ''}://#{local_ip}:#{port}/mayfly/")
|
35
|
+
|
36
|
+
logger.mayfly_log("Using HTTP Authentication with the username 'mayfly'" +
|
37
|
+
" and the password you specified") if @passwd
|
38
|
+
|
39
|
+
|
40
|
+
logger.info("Starting mayfly with the following settings: ")
|
41
|
+
logger.info("file=#{file}")
|
42
|
+
logger.info("port=#{port}")
|
43
|
+
logger.info("lives=#{lives}")
|
44
|
+
logger.info("secure=#{secure}")
|
45
|
+
logger.info("passwd=#{passwd}")
|
46
|
+
|
47
|
+
@config = {
|
48
|
+
:Port => port,
|
49
|
+
:Logger => logger,
|
50
|
+
:AccessLog => [[$stdout, "%t Request from %h, response code = %s"]],
|
51
|
+
}
|
52
|
+
|
53
|
+
if (secure)
|
54
|
+
|
55
|
+
cert, key = get_cert
|
56
|
+
|
57
|
+
@config.merge!({
|
58
|
+
:SSLEnable => true,
|
59
|
+
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
|
60
|
+
:SSLCertificate => cert,
|
61
|
+
:SSLPrivateKey => key
|
62
|
+
})
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def start
|
69
|
+
|
70
|
+
server = WEBrick::HTTPServer.new(@config)
|
71
|
+
server.mount('/mayfly', Mayfly::Servlet, @file, @lives, @passwd)
|
72
|
+
|
73
|
+
['INT', 'TERM'].each { |signal|
|
74
|
+
trap(signal){server.shutdown}
|
75
|
+
}
|
76
|
+
|
77
|
+
server.start
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
# Generate SSL Cert, and hack stderr to not output some ugly crap from webrick
|
82
|
+
def get_cert
|
83
|
+
old_stderr = $stderr
|
84
|
+
$stderr = StringIO.new
|
85
|
+
cert, key = WEBrick::Utils.create_self_signed_cert(1024, [["C","GB"], ["O","#{local_ip}"], ["CN", "Mayfly"]], "Generated by Ruby/OpenSSL")
|
86
|
+
$stderr = old_stderr
|
87
|
+
return cert, key
|
88
|
+
end
|
89
|
+
|
90
|
+
def logger
|
91
|
+
@logger ||= Log.new((@verbose) ? $stdout : nil, WEBrick::Log::INFO)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/lib/mayfly/file.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Mayfly
|
2
|
+
|
3
|
+
class File < File
|
4
|
+
|
5
|
+
@close_callback
|
6
|
+
|
7
|
+
def initialize(file, close_callback = nil)
|
8
|
+
super file
|
9
|
+
@close_callback = close_callback
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
super
|
14
|
+
@close_callback.call if @close_callback != nil
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Mayfly
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'thread'
|
5
|
+
require 'webrick/httputils'
|
6
|
+
require 'mayfly/utils'
|
7
|
+
include Mayfly::Utils
|
8
|
+
|
9
|
+
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
10
|
+
|
11
|
+
@@instance = nil
|
12
|
+
@@instance_creation_mutex = Mutex.new
|
13
|
+
|
14
|
+
def self.get_instance(config, *options)
|
15
|
+
@@instance_creation_mutex.synchronize {
|
16
|
+
@@instance = @@instance || self.new(config, *options)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(config, file, max, passwd = nil)
|
21
|
+
super
|
22
|
+
@file = file
|
23
|
+
@max = max
|
24
|
+
@count = 1
|
25
|
+
@count_mutex = Mutex.new
|
26
|
+
@passwd = passwd
|
27
|
+
end
|
28
|
+
|
29
|
+
def do_GET(req, resp)
|
30
|
+
|
31
|
+
if (@count > @max)
|
32
|
+
resp.status = 401
|
33
|
+
end
|
34
|
+
|
35
|
+
if (@passwd)
|
36
|
+
WEBrick::HTTPAuth.basic_auth(req, resp, 'mayfly') do |user, pass|
|
37
|
+
user == 'mayfly' && pass == @passwd
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
st = File::stat(@file)
|
42
|
+
resp['content-type'] = WEBrick::HTTPUtils::mime_type(@file, WEBrick::HTTPUtils::DefaultMimeTypes)
|
43
|
+
resp['content-length'] = st.size
|
44
|
+
resp['content-disposition'] = "attachment; filename=\"#{File.basename(@file)}\""
|
45
|
+
resp['last-modified'] = st.mtime.httpdate
|
46
|
+
resp['Cache-Control'] = 'no-cache, must-revalidate'
|
47
|
+
|
48
|
+
close_callback = nil
|
49
|
+
|
50
|
+
@count_mutex.synchronize {
|
51
|
+
@count += 1
|
52
|
+
close_callback = Proc.new do
|
53
|
+
growl("mayfly served #{@file} to #{req.peeraddr[2]}")
|
54
|
+
if (@count > @max)
|
55
|
+
@server.shutdown if (@count >= @max)
|
56
|
+
growl("mayfly has died, it served #{@file} #{@max} times")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
}
|
60
|
+
|
61
|
+
if (@count <= @max + 1)
|
62
|
+
resp.body = File.new(@file, close_callback)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/lib/mayfly/utils.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Mayfly
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'growl', '=1.0.3'
|
6
|
+
require 'growl'
|
7
|
+
include Growl
|
8
|
+
|
9
|
+
require 'socket'
|
10
|
+
|
11
|
+
def growl(msg)
|
12
|
+
if Growl.installed?
|
13
|
+
notify_info(msg, {:title => 'mayfly'})
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
|
18
|
+
def local_ip
|
19
|
+
|
20
|
+
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
|
21
|
+
|
22
|
+
UDPSocket.open do |s|
|
23
|
+
s.connect '64.233.187.99', 1
|
24
|
+
s.addr.last
|
25
|
+
end
|
26
|
+
|
27
|
+
ensure
|
28
|
+
Socket.do_not_reverse_lookup = orig
|
29
|
+
end
|
30
|
+
module_function :local_ip
|
31
|
+
|
32
|
+
# Some helpers for the cli
|
33
|
+
class Cli
|
34
|
+
|
35
|
+
gem 'highline', '=1.5.1'
|
36
|
+
require 'highline/import'
|
37
|
+
|
38
|
+
def self.get_passwd
|
39
|
+
passwd = ask('Password, leave blank for none: ') {|q| q.echo = false}
|
40
|
+
return nil if passwd == ''
|
41
|
+
if (passwd != ask('Confirm password: ') {|q| q.echo = false})
|
42
|
+
puts 'Passwords do not match, exiting'
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
passwd
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mayfly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Haynes
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-18 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: growl
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: highline
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.5.1
|
34
|
+
version:
|
35
|
+
description: "mayfly is a tiny HTTP server with a very short lifespan, existing only to serve a single file for a predefined number of times, it then quietly shuffles off this mortal coil. "
|
36
|
+
email: matt@matthaynes.net
|
37
|
+
executables:
|
38
|
+
- mayfly
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- lib/mayfly.rb
|
45
|
+
- lib/mayfly/file.rb
|
46
|
+
- lib/mayfly/servlet.rb
|
47
|
+
- lib/mayfly/utils.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/matth/mayfly/tree/master
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.3.5
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: A tiny HTTP server with a very short lifespan
|
76
|
+
test_files: []
|
77
|
+
|