femtows 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +5 -0
- data/README.md +69 -0
- data/VERSION +1 -0
- data/bin/femtows.bat +2 -0
- data/bin/femtows.sh +1 -0
- data/demo.rb +33 -0
- data/femtows.gemspec +30 -0
- data/femtows.log +7876 -0
- data/gitc.bat +20 -0
- data/guidemo.rb +63 -0
- data/lib/femtows.rb +256 -0
- metadata +58 -0
data/gitc.bat
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
@echo off
|
2
|
+
IF DEFINED %1=="" (
|
3
|
+
call giti
|
4
|
+
ruby -e "a=File.read('VERSION').split('.') ; a[-1]=(a.last.to_i+1).to_s; puts r=a.join('.'); File.open('VERSION','w') {|f| f.write(r)}"
|
5
|
+
echo %1 %2 %3 %4 %5 %6 %7 %8 %9 >> CHANGELOG.txt
|
6
|
+
git commit -a -m "%1 %2 %3 %4 %5 %6 %7 %8 %9"
|
7
|
+
git push
|
8
|
+
echo
|
9
|
+
echo call gitc.bat without args for make/post rubygem
|
10
|
+
goto :eof
|
11
|
+
)
|
12
|
+
:gem
|
13
|
+
rem ==== no args, generate gem and push it to rubygems.org
|
14
|
+
|
15
|
+
ruby -e "a=File.read('VERSION').split('.');a.pop ; a[-1]=(a.last.to_i+1).to_s; puts r=(a+[0]).join('.'); File.open('VERSION','w') {|f| f.puts(r)}"
|
16
|
+
cat VERSION >> CHANGELOG.txt
|
17
|
+
|
18
|
+
ruby -e "Dir.glob('femtows*.gem').each {|f| File.delete(f) }"
|
19
|
+
call gem build femtows.gemspec
|
20
|
+
call gem push femtows*.gem
|
data/guidemo.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative '../../Ruiby/lib/ruiby.rb'
|
2
|
+
require_relative "lib/femtows.rb"
|
3
|
+
#require 'ruiby'
|
4
|
+
ruiby_require 'erubis','xxx'
|
5
|
+
|
6
|
+
class Server < Ruiby_gtk
|
7
|
+
def initialize(t,w,h)
|
8
|
+
$server=self
|
9
|
+
@port=8100
|
10
|
+
@root="/"
|
11
|
+
super
|
12
|
+
threader(50)
|
13
|
+
end
|
14
|
+
def component
|
15
|
+
stack {
|
16
|
+
flowi {
|
17
|
+
table(2,2) {
|
18
|
+
row { cell_right label("root") ; cell(@eroot=entry(@root)) }
|
19
|
+
row { cell_right label("port") ; cell(@eport=ientry(@port,:min=>0,:max=>65535,:by=>10)) }
|
20
|
+
}
|
21
|
+
button("Restart") {
|
22
|
+
@root,@port=@eroot.text,@eport.text.to_i
|
23
|
+
$ws.stop_browser if $ws
|
24
|
+
run_server
|
25
|
+
deflog("","","restart ok")
|
26
|
+
}
|
27
|
+
button("reset log") { @logt.buffer.text="resetted" }
|
28
|
+
}
|
29
|
+
@logt= slot(text_area(800,160,{font: "courier new 8"}))
|
30
|
+
}
|
31
|
+
end
|
32
|
+
def run_server
|
33
|
+
after(1) do
|
34
|
+
begin
|
35
|
+
$ws=WebserverRoot.new(@port,@root,"femto ws",10,300, {
|
36
|
+
"logg" => proc {|*par| deflog(*par) }
|
37
|
+
})
|
38
|
+
$ws.serve("/info") {|p|
|
39
|
+
[200,".html", "Femto demo<hr><a href='/'>site</a><hr>#{$ws.to_table(p)}" ]
|
40
|
+
}
|
41
|
+
deflog('','',"restarted!")
|
42
|
+
rescue
|
43
|
+
deflog(["","",$!.to_s])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
def logs(mess)
|
48
|
+
@logt.append(mess)
|
49
|
+
if @logt.text.size>100*1000
|
50
|
+
@logt.text=@logt.text[500..-1]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def deflog(name,adr,*res)
|
55
|
+
mess= "%s |%15s | %s\n" % [Time.now.strftime("%Y-%m-%d %H:%M:%S"),adr,res.join(" ")]
|
56
|
+
gui_invoke { logs(mess) rescue print $!.to_s+ $!.backtrace.join("\n")+"\n /// "+mess}
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
########################### main
|
61
|
+
Thread.abort_on_exception = false
|
62
|
+
BasicSocket.do_not_reverse_lookup = true
|
63
|
+
Ruiby.start { Server.new("Web server",800,200).run_server }
|
data/lib/femtows.rb
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# FemtoWebServer : 232 LOC web server
|
3
|
+
#
|
4
|
+
# $ws=WebserverRoot.new(port,"/home/www","femto ws",10,300)
|
5
|
+
# ws.serve "/FOO" do |params|
|
6
|
+
# data=params["HEAD-DATA"]|| ""
|
7
|
+
# puts "Recu data len=#{data.length} : <#{data[0..1000]}>" if data
|
8
|
+
# [200,".json",""]
|
9
|
+
# end
|
10
|
+
|
11
|
+
|
12
|
+
require 'thread'
|
13
|
+
require 'socket'
|
14
|
+
require 'timeout'
|
15
|
+
|
16
|
+
#################### Tiny embeded webserver
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
class WebserverAbstract
|
22
|
+
def logg(*args)
|
23
|
+
if @cb_log then @cb_log.call(@name,*args) else puts(args.join(" ")) end
|
24
|
+
rescue
|
25
|
+
puts(args.join(" "))
|
26
|
+
end
|
27
|
+
def info(txt) ; logg("nw>i>",txt) ; end
|
28
|
+
def error(txt) ; logg("nw>e>",txt) ; end
|
29
|
+
def unescape(string) ; string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2}))/n) { [$1.delete('%')].pack('H*') } ; end
|
30
|
+
def escape(string) ; string.gsub(/([^ \/a-zA-Z0-9_.-]+)/) { '%' + $1.unpack('H2' * $1.size).join('%').upcase }.tr(' ', '+'); end
|
31
|
+
def hescape(string) ; escape(string.gsub("/./","/").gsub("//","/")) ; end
|
32
|
+
def observe(sleeping,delta)
|
33
|
+
@tho=Thread.new do loop do
|
34
|
+
sleep(sleeping)
|
35
|
+
nowDelta=Time.now-delta
|
36
|
+
l=@th.select { |th,tm| (tm[0]<nowDelta) }
|
37
|
+
l.each { |th,tm| info("killing thread") ; th.kill; @th.delete(th) ; tm[1].close rescue nil }
|
38
|
+
end ; end
|
39
|
+
end
|
40
|
+
def initialize(port,root,name,cadence,timeout,options)
|
41
|
+
raise("tcp port illegal #{port}") unless port.to_i>=80
|
42
|
+
raise("root not exist #{root}") unless File.exists?(root)
|
43
|
+
@cb_log= options["logg"]
|
44
|
+
@last_mtime=File.mtime(__FILE__)
|
45
|
+
@port=port.to_i
|
46
|
+
@root=root
|
47
|
+
@name=name
|
48
|
+
@rootd=root[-1,1]=="/" ? root : root+"/"
|
49
|
+
@timeout=timeout
|
50
|
+
@th={}
|
51
|
+
@cb={}
|
52
|
+
@redirect={}
|
53
|
+
info(" serveur http #{port} on #{@rootd} ready!")
|
54
|
+
observe(cadence,timeout*2)
|
55
|
+
@thm=Thread.new {
|
56
|
+
loop {
|
57
|
+
nbError=0
|
58
|
+
begin
|
59
|
+
session=nil
|
60
|
+
@server = TCPServer.new('0.0.0.0', @port)
|
61
|
+
@server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
62
|
+
while (session = @server.accept)
|
63
|
+
nbError=0
|
64
|
+
run(session)
|
65
|
+
end
|
66
|
+
rescue Exception => e
|
67
|
+
nbError+=1
|
68
|
+
error($!.to_s + " " + $!.backtrace[0..2].join(" "))
|
69
|
+
session.close rescue nil
|
70
|
+
@server.close rescue nil
|
71
|
+
end
|
72
|
+
sleep(3); info("restart accept")
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
def run(session)
|
77
|
+
if ! File.exists?(@root)
|
78
|
+
sendError(session,500,txt="root directory unknown: #{@root}") rescue nil
|
79
|
+
session.close rescue nil
|
80
|
+
else
|
81
|
+
Thread.new(session) do |sess|
|
82
|
+
@th[Thread.current]=[Time.now,sess]
|
83
|
+
request(sess)
|
84
|
+
@th.delete(Thread.current)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
def serve(uri,&blk)
|
89
|
+
@cb[uri] = blk
|
90
|
+
end
|
91
|
+
def request(session)
|
92
|
+
request = session.gets
|
93
|
+
uri = (request.split(/\s+/)+['','',''])[1]
|
94
|
+
#info uri
|
95
|
+
service,param,*bidon=(uri+"?").split(/\?/)
|
96
|
+
params=Hash[*(param.split(/#/)[0].split(/[=&]/))] rescue {}
|
97
|
+
params.each { |k,v| params[k]=unescape(v) }
|
98
|
+
uri=unescape(service)[1..-1].gsub(/\.\./,"")
|
99
|
+
userpass=nil
|
100
|
+
if (buri=uri.split(/@/)).size>1
|
101
|
+
uri=buri[1..-1].join("@")
|
102
|
+
userpass=buri[0].split(/:/)
|
103
|
+
end
|
104
|
+
read_header(session,params)
|
105
|
+
do_service(session,request,uri,userpass,params)
|
106
|
+
rescue Exception => e
|
107
|
+
error("Error Web get on #{request}: \n #{$!.to_s} \n #{$!.backtrace.join("\n ")}" ) rescue nil
|
108
|
+
session.write "HTTP/1.0 501 NOK\r\nContent-type: text/html\r\n\r\n<html><head><title>WS</title></head><body>Error : #{$!}" rescue nil
|
109
|
+
ensure
|
110
|
+
session.close rescue nil
|
111
|
+
end
|
112
|
+
def read_header(session,params)
|
113
|
+
head=session.gets("\r\n\r\n")
|
114
|
+
head.split(/\r\n/m).each { |line| name,data=line.split(": ",2) ; params["HEAD-"+name.upcase]=data }
|
115
|
+
if params["HEAD-CONTENT-LENGTH"]
|
116
|
+
len= params["HEAD-CONTENT-LENGTH"].split(/\s+/).last.to_i
|
117
|
+
params["HEAD-CONTENT-LENGTH"]=len
|
118
|
+
data=""
|
119
|
+
while len>0
|
120
|
+
d=session.read(len>64*1024 ? 64*1024 : len)
|
121
|
+
raise("closed") if !d
|
122
|
+
len -= d.length
|
123
|
+
data+=d
|
124
|
+
end
|
125
|
+
params["HEAD-DATA"]=data
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def redirect(o,d)
|
130
|
+
@redirect[o]=d
|
131
|
+
end
|
132
|
+
def do_service(session,request,service,user_passwd,params)
|
133
|
+
logg(session.peeraddr.last,request.chomp)
|
134
|
+
redir=@redirect["/"+service]
|
135
|
+
service=redir.gsub(/^\//,"") if @redirect[redir]
|
136
|
+
aservice=to_absolute(service)
|
137
|
+
if redir && ! @redirect[redir]
|
138
|
+
do_service(session,request,redir.gsub(/^\//,""),user_passwd,params)
|
139
|
+
elsif @cb["/"+service]
|
140
|
+
begin
|
141
|
+
code,type,data= @cb["/"+service].call(params)
|
142
|
+
if code==0 && data != '/'+service
|
143
|
+
do_service(session,request,data[1..-1],user_passwd,params)
|
144
|
+
else
|
145
|
+
code==200 ? sendData(session,type,data) : sendError(session,code,data)
|
146
|
+
end
|
147
|
+
rescue
|
148
|
+
logg session.peeraddr.last,"Error in get /#{service} : #{$!}"
|
149
|
+
sendError(session,501,$!.to_s)
|
150
|
+
end
|
151
|
+
elsif service =~ /^stop/
|
152
|
+
sendData(session,".html","Stopping...");
|
153
|
+
Thread.new() { sleep(0.1); stop_browser() }
|
154
|
+
elsif File.directory?(aservice)
|
155
|
+
sendData(session,".html",makeIndex(aservice))
|
156
|
+
elsif File.exists?(aservice)
|
157
|
+
sendFile(session,aservice)
|
158
|
+
else
|
159
|
+
info("unknown request serv=#{service} params=#{params.inspect} #{File.exists?(service)}")
|
160
|
+
sendError(session,500,"unknown request serv=#{aservice} params=#{params.inspect} #{File.exists?(service)}");
|
161
|
+
end
|
162
|
+
end
|
163
|
+
def stop_browser
|
164
|
+
info "exit on web demand !"
|
165
|
+
[@tho,@thm].each { |th| th.kill }
|
166
|
+
@server.close rescue nil
|
167
|
+
end
|
168
|
+
def makeIndex(adir)
|
169
|
+
dir=to_relative(adir)
|
170
|
+
dirs,files=Dir.glob(adir==@rootd ? "#{@rootd}*" : "#{adir}/*").sort.partition { |f| File.directory?(f)}
|
171
|
+
|
172
|
+
updir = hescape( dir.split(/\//)[0..-2].join("/"))
|
173
|
+
updir="/" if updir.length==0
|
174
|
+
up=(dir!="/") ? "<input type='button' onclick='location.href=\"#{updir}\"' value='Parent'>" : ""
|
175
|
+
"<html><head><title>#{dir}</title></head>\n<body><h3><center>#{@name} : #{dir[0..-1]}</center></h3>\n<hr>#{up}<br>#{to_table(dirs.map {|s| " <a href='#{hescape(to_relative(s))}'>"+File.basename(s)+"/"+"</a>\n"})}<hr>#{to_tableb(files) {|f| [" <a href='#{hescape(to_relative(f))}'>"+File.basename(f)+"</a>",n3(File.size(f)),File.mtime(f).strftime("%d/%m/%Y %H:%M:%S")]}}</body></html>"
|
176
|
+
end
|
177
|
+
def to_relative(f) f.gsub(/^#{@rootd}/,"/") end
|
178
|
+
def to_absolute(f) "#{@rootd}#{f.gsub(/^\//,'')}" end
|
179
|
+
def n3(n)
|
180
|
+
u=" B"
|
181
|
+
if n> 10000000
|
182
|
+
n=n/(1024*1024)
|
183
|
+
u=" MB"
|
184
|
+
elsif n> 100000
|
185
|
+
n=n/1024
|
186
|
+
u=" KB"
|
187
|
+
end
|
188
|
+
"<div style='width:100px;text-align:right;'>#{(n.round.to_i.to_s.reverse.gsub(/(\d\d\d)(?=\d)/,'\1 ' ).reverse) +u} | </div>"
|
189
|
+
end
|
190
|
+
def to_table(l)
|
191
|
+
"<table><tr>#{l.map {|s| "<td>#{s}</td>"}.join("</tr><tr>")}</tr></table>"
|
192
|
+
end
|
193
|
+
def to_tableb(l,&bl)
|
194
|
+
"<table><tr>#{l.map {|s| "<td>#{bl.call(s).join("</td><td>")}</td>"}.join("</tr><tr>")}</tr></table>"
|
195
|
+
end
|
196
|
+
def sendError(sock,no,txt=nil)
|
197
|
+
if txt
|
198
|
+
txt="<html><body><code><pre></pre>#{txt}</code></body></html>"
|
199
|
+
end
|
200
|
+
sock.write "HTTP/1.0 #{no} NOK\r\nContent-type: #{mime(".html")}\r\n\r\n <html><p>Error #{no} : #{txt}</p></html>"
|
201
|
+
end
|
202
|
+
def sendData(sock,type,content)
|
203
|
+
sock.write "HTTP/1.0 200 OK\r\nContent-Type: #{mime(type)}\r\nContent-Length: #{content.size}\r\n\r\n"
|
204
|
+
sock.write(content)
|
205
|
+
end
|
206
|
+
def sendFile(sock,filename)
|
207
|
+
s=File.size(filename)
|
208
|
+
if s < 0 || s>60_000_000 || File.extname(filename).downcase==".lnk"
|
209
|
+
logg @name,"Error reading file/File not downloadable #{File.basename(filename)} : (size=#{s})"
|
210
|
+
sendError(sock,500,"Error reading file/File not downloadable #{filename} : (size=#{s})" )
|
211
|
+
return
|
212
|
+
end
|
213
|
+
logg @name,filename," #{s/(1024*1024)} Mo" if s>10*1000_000
|
214
|
+
timeout([s/(512*1024),30.0].max.to_i) {
|
215
|
+
sock.write "HTTP/1.0 200 OK\r\nContent-Type: #{mime(filename)}\r\nContent-Length: #{File.size(filename)}\r\nLast-Modified: #{httpdate(File.mtime(filename))}\r\nDate: #{httpdate(Time.now)}\r\n\r\n"
|
216
|
+
File.open(filename,"rb") do |f|
|
217
|
+
f.binmode; sock.binmode;
|
218
|
+
( sock.write(f.read(32*1024)) while (! f.eof? && ! sock.closed?) ) rescue nil
|
219
|
+
end
|
220
|
+
}
|
221
|
+
end
|
222
|
+
def httpdate( aTime ); (aTime||Time.now).gmtime.strftime( "%a, %d %b %Y %H:%M:%S GMT" ); end
|
223
|
+
def mime(string)
|
224
|
+
MIME[string.split(/\./).last] || "application/octet-stream"
|
225
|
+
end
|
226
|
+
LICON="☀☃☎☑☑☠☣☮☺☂".split(/;/).map {|c| c+";"}
|
227
|
+
MIME={"png" => "image/png", "gif" => "image/gif", "html" => "text/html","htm" => "text/html",
|
228
|
+
"js" => "text/javascript" ,"css" => "text/css","jpeg" => "image/jpeg" ,"jpg" => "image/jpeg",
|
229
|
+
".json" => "applicatipon/json",
|
230
|
+
"pdf"=> "application/pdf" , "svg" => "image/svg+xml","svgz" => "image/svg+xml",
|
231
|
+
"xml" => "text/xml" ,"xsl" => "text/xml" ,"bmp" => "image/bmp" ,"txt" => "text/plain" ,
|
232
|
+
"rb" => "text/plain" ,"pas" => "text/plain" ,"tcl" => "text/plain" ,"java" => "text/plain" ,
|
233
|
+
"c" => "text/plain" ,"h" => "text/plain" ,"cpp" => "text/plain", "xul" => "application/vnd.mozilla.xul+xml",
|
234
|
+
"doc" => "application/msword", "docx" => "application/msword","dot"=> "application/msword",
|
235
|
+
"xls" => "application/vnd.ms-excel","xla" => "application/vnd.ms-excel","xlt" => "application/vnd.ms-excel","xlsx" => "application/vnd.ms-excel",
|
236
|
+
"ppt" => "application/vnd.ms-powerpoint", "pptx" => "application/vnd.ms-powerpoint"
|
237
|
+
}
|
238
|
+
end # 220 loc webserver :)
|
239
|
+
|
240
|
+
class Webserver < WebserverAbstract
|
241
|
+
def initialize(port=7080,cadence=10,timeout=120)
|
242
|
+
super(port,Dir.getwd(),"",cadence,timeout)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
class WebserverRoot < WebserverAbstract
|
246
|
+
def initialize(port=7080,root=".",name="wwww",cadence=10,timeout=120,options={})
|
247
|
+
super(port,root,name,cadence,timeout,options)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
def cliweb(root=Dir.getwd,port=59999)
|
251
|
+
Thread.abort_on_exception = false
|
252
|
+
BasicSocket.do_not_reverse_lookup = true
|
253
|
+
$ws=WebserverRoot.new(port,root,'femto ws',10,300, {});
|
254
|
+
puts "Server root path #{root} with port #{port}"
|
255
|
+
sleep
|
256
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: femtows
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Regis d'Aubarede
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-14 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! "a tiny web server, for local file transfert, \nembededded, http experimentations\n"
|
15
|
+
email: regis.aubarede@gmail.com
|
16
|
+
executables:
|
17
|
+
- femtows.bat
|
18
|
+
- femtows.sh
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- bin/femtows.bat
|
23
|
+
- bin/femtows.sh
|
24
|
+
- CHANGELOG.txt
|
25
|
+
- demo.rb
|
26
|
+
- femtows.gemspec
|
27
|
+
- femtows.log
|
28
|
+
- gitc.bat
|
29
|
+
- guidemo.rb
|
30
|
+
- lib/femtows.rb
|
31
|
+
- README.md
|
32
|
+
- VERSION
|
33
|
+
homepage: http://github.com/raubarede/femtows
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.8.15
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: a tiny webserver
|
57
|
+
test_files: []
|
58
|
+
has_rdoc:
|