femtows 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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="&#9728;&#9731;&#9742;&#9745;&#9745;&#9760;&#9763;&#9774;&#9786;&#9730;".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: