├── test ├── TC_Swiftiply │ ├── test_ssl │ │ ├── pub │ │ │ └── .placeholder │ │ ├── bin │ │ │ └── validate_ssl_capability.rb │ │ ├── test.key │ │ └── test.cert │ ├── test_serve_static_file │ │ └── .placeholder │ ├── test_serve_normal_proxy │ │ └── .placeholder │ ├── test_serve_static_file_xsendfile │ │ ├── priv │ │ │ └── .placeholder │ │ ├── pub │ │ │ └── .placeholder │ │ └── sendfile_client.rb │ ├── test_serve_normal_proxy_with_authentication │ │ └── .placeholder │ ├── test_serve_static_file_from_cachedir │ │ └── public │ │ │ └── .placeholder │ ├── mongrel │ │ ├── threaded_hello.rb │ │ ├── evented_hello.rb │ │ └── swiftiplied_hello.rb │ └── slow_echo_client ├── bin │ └── echo_client ├── TC_Deque.rb └── TC_ProxyBag.rb ├── cleanup.sh ├── src ├── swiftcore │ ├── Swiftiply │ │ ├── version.rb │ │ ├── swiftiply_2_http_proxy.rb │ │ ├── cluster_managers │ │ │ └── rest_based_cluster_manager.rb │ │ ├── mocklog.rb │ │ ├── loggers │ │ │ ├── stderror.rb │ │ │ └── Analogger.rb │ │ ├── rest_based_cluster_manager.rb │ │ ├── config │ │ │ └── rest_updater.rb │ │ ├── splay_cache_base.rb │ │ ├── proxy.rb │ │ ├── hash_cache_base.rb │ │ ├── dynamic_request_cache.rb │ │ ├── file_cache.rb │ │ ├── content_response.rb │ │ ├── cache_base.rb │ │ ├── control_protocol.rb │ │ ├── cache_base_mixin.rb │ │ ├── content_cache_entry.rb │ │ ├── proxy_backends │ │ │ ├── traditional │ │ │ │ ├── static_directory.rb │ │ │ │ └── redis_directory.rb │ │ │ ├── traditional.rb │ │ │ └── keepalive.rb │ │ ├── etag_cache.rb │ │ ├── support_pagecache.rb │ │ ├── swiftiply_client.rb │ │ ├── cluster_protocol.rb │ │ ├── constants.rb │ │ ├── backend_protocol.rb │ │ └── http_recognizer.rb │ ├── method_builder.rb │ ├── streamer.rb │ ├── hash.rb │ ├── evented_mongrel.rb │ └── swiftiplied_mongrel.rb └── fastfilereader.rb ├── ext ├── splaytree │ ├── swiftcore │ │ └── splay_tree.h │ └── extconf.rb ├── fastfilereader │ ├── mapper.h │ ├── rubymain.cpp │ ├── extconf.rb │ └── mapper.cpp ├── map │ ├── extconf.rb │ └── rubymain.cpp └── deque │ ├── extconf.rb │ └── swiftcore │ └── rubymain.cpp ├── CONTRIBUTORS ├── setup.rb ├── swiftiply.gemspec ├── external ├── test_support.rb └── httpclient.rb ├── bin ├── swiftiply_mongrel_rails ├── swiftiply ├── swiftiplyctl ├── evented_mongrel_rails └── swiftiplied_mongrel_rails └── README.md /test/TC_Swiftiply/test_ssl/pub/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_serve_static_file/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_serve_normal_proxy/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_serve_static_file_xsendfile/priv/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_serve_static_file_xsendfile/pub/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_serve_normal_proxy_with_authentication/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_serve_static_file_from_cachedir/public/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm ext/*/*.*o 3 | rm ext/*/*.*bundle 4 | rm ext/*/*.log 5 | rm ext/*/Makefile 6 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/version.rb: -------------------------------------------------------------------------------- 1 | module Swiftcore 2 | module Swiftiply 3 | VERSION = '1.1.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ext/splaytree/swiftcore/splay_tree.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyhaines/swiftiply/HEAD/ext/splaytree/swiftcore/splay_tree.h -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/swiftiply_2_http_proxy.rb: -------------------------------------------------------------------------------- 1 | module Swiftiply 2 | class Swiftiply2HttpProxy 3 | def self.run(config) 4 | end 5 | 6 | end 7 | end -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/cluster_managers/rest_based_cluster_manager.rb: -------------------------------------------------------------------------------- 1 | module Swiftcore 2 | module Swiftiply 3 | module ClusterManagers 4 | class RestBasedClusterManager 5 | 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/mocklog.rb: -------------------------------------------------------------------------------- 1 | # This is just a mock log to provide a bit bucket logging location, if needed. 2 | 3 | module Swiftcore 4 | module Swiftiply 5 | class MockLog 6 | def log(*args) 7 | end 8 | end 9 | end 10 | end -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Swiftiply is written by Kirk Haines (wyhaines@gmail.com). 2 | 3 | The fastfilereader extension was contributed by Francis Cianfrocca. 4 | 5 | Ezra Zygmuntowicz contributed swiftiply_mongrel_rails and edits to 6 | mongrel_rails. 7 | 8 | Flip Sasser contributed the original version of the swiftiplyctl. 9 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/loggers/stderror.rb: -------------------------------------------------------------------------------- 1 | module Swiftcore 2 | module Swiftiply 3 | module Loggers 4 | class Stderror 5 | def initialize(*args);end 6 | 7 | def log(severity, msg) 8 | $stderr.puts "#{Time.now.asctime}:#{severity}:#{msg}" 9 | end 10 | end 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/rest_based_cluster_manager.rb: -------------------------------------------------------------------------------- 1 | require 'em-http' 2 | module Swiftcore 3 | module Swiftiply 4 | module ManagerProtocols 5 | class RestBasedClusterManager 6 | def self.call(callsite, params) 7 | EventMachine::HttpRequest.new(callsite).get 8 | true 9 | rescue Exception 10 | false 11 | end 12 | end 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_ssl/bin/validate_ssl_capability.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'eventmachine' 3 | require 'net/http' 4 | require 'net/https' 5 | 6 | class P < EventMachine::Connection 7 | def post_init; start_tls; end 8 | 9 | def receive_data x 10 | send_data("HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\nBoo!") 11 | close_connection_after_writing 12 | end 13 | 14 | end 15 | 16 | EM.run do 17 | EM.start_server('127.0.0.1',3333,P) 18 | EM.add_timer(6) {EM.stop_event_loop} 19 | end 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/config/rest_updater.rb: -------------------------------------------------------------------------------- 1 | module Swiftcore 2 | module Swiftiply 3 | class Config 4 | class RestUpdater 5 | 6 | def initialize(config) 7 | @config = config 8 | end 9 | 10 | def start 11 | host = @config[Chost] || '127.0.0.1' 12 | port = @config[Cport] || 9949 13 | end 14 | 15 | class RestUpdaterProtocol < EventMachine::Connection 16 | def receive_data data 17 | 18 | end 19 | end 20 | 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/splay_cache_base.rb: -------------------------------------------------------------------------------- 1 | require 'swiftcore/Swiftiply/cache_base_mixin' 2 | 3 | module Swiftcore 4 | # Use Array instead of Deque, if Deque wasn't available. 5 | Deque = Array unless HasDeque or const_defined?(:Deque) 6 | 7 | module Swiftiply 8 | class CacheBase < Swiftcore::SplayTreeMap 9 | include CacheBaseMixin 10 | def initialize(vw = 900, time_limit = 0.05, maxsize = 100) 11 | @vw = vw 12 | @tl = time_limit 13 | @vwtl = vw * time_limit 14 | @old_vql = 0 15 | @vq = Deque.new 16 | super() 17 | self.max_size = maxsize 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/proxy.rb: -------------------------------------------------------------------------------- 1 | # Super class for all proxy implementations. 2 | module Swiftcore 3 | module Swiftiply 4 | class Proxy 5 | def self.config(conf, new_conf) 6 | # Process Proxy config; determine which specific proxy implementation to load and pass config control into. 7 | conf[Cproxy] ||= Ckeepalive if conf[Ckeepalive] 8 | require "swiftcore/Swiftiply/proxy_backends/#{conf[Cproxy]}" 9 | klass = Swiftcore::Swiftiply::get_const_from_name(conf[Cproxy].upcase, ::Swiftcore::Swiftiply::Proxies) 10 | # Need error handling here! 11 | klass.config(conf, new_conf) 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/hash_cache_base.rb: -------------------------------------------------------------------------------- 1 | require 'swiftcore/Swiftiply/cache_base_mixin' 2 | 3 | module Swiftcore 4 | # Use Array instead of Deque, if Deque wasn't available. 5 | Deque = Array unless HasDeque or const_defined?(:Deque) 6 | 7 | module Swiftiply 8 | class CacheBase < Hash 9 | include CacheBaseMixin 10 | 11 | def initialize(vw = 900, time_limit = 0.05, maxsize = nil) 12 | @vw = vw 13 | @tl = time_limit 14 | @wvtl = vw * time_limit 15 | @old_vql = 0 16 | @vq = Deque.new 17 | @maxsize = maxsize #max size is irrelevant for a vanilla hash, but it'll be tracked anyway 18 | super() 19 | end 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/loggers/Analogger.rb: -------------------------------------------------------------------------------- 1 | # The Swiftiply logging support is really written with Analogger in mind, since 2 | # the ideal logging solution should provide a minimal performance impact. 3 | 4 | require 'swiftcore/Analogger/Client' 5 | 6 | module Swiftcore 7 | module Swiftiply 8 | module Loggers 9 | class Analogger 10 | def self.new(params) 11 | lp = [] 12 | lp << params['service'] || 'swiftiply' 13 | lp << params['host'] || '127.0.0.1' 14 | lp << (params['port'] && params['port'].to_i) || 6766 15 | lp << params['key'] 16 | ::Swiftcore::Analogger::Client.new(*lp) 17 | end 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /test/TC_Swiftiply/mongrel/threaded_hello.rb: -------------------------------------------------------------------------------- 1 | begin 2 | load_attempted ||= false 3 | require 'mongrel' 4 | rescue LoadError => e 5 | unless load_attempted 6 | load_attempted = true 7 | require 'rubygems' 8 | retry 9 | end 10 | raise e 11 | end 12 | 13 | class SimpleHandler < Mongrel::HttpHandler 14 | def process(request, response) 15 | response.start(200) do |head,out| 16 | head["Content-Type"] = "text/plain" 17 | out.write("hello!\n") 18 | end 19 | end 20 | end 21 | 22 | httpserver = Mongrel::HttpServer.new("127.0.0.1", 29997) 23 | httpserver.register("/hello", SimpleHandler.new) 24 | httpserver.register("/dir", Mongrel::DirHandler.new(".")) 25 | httpserver.run.join 26 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/mongrel/evented_hello.rb: -------------------------------------------------------------------------------- 1 | begin 2 | load_attempted ||= false 3 | require 'swiftcore/evented_mongrel' 4 | rescue LoadError => e 5 | unless load_attempted 6 | load_attempted = true 7 | require 'rubygems' 8 | retry 9 | end 10 | raise e 11 | end 12 | 13 | class SimpleHandler < Mongrel::HttpHandler 14 | def process(request, response) 15 | response.start(200) do |head,out| 16 | head["Content-Type"] = "text/plain" 17 | out.write("hello!\n") 18 | end 19 | end 20 | end 21 | 22 | httpserver = Mongrel::HttpServer.new("127.0.0.1", 29998) 23 | httpserver.register("/hello", SimpleHandler.new) 24 | httpserver.register("/dir", Mongrel::DirHandler.new(".")) 25 | httpserver.run.join 26 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/slow_echo_client: -------------------------------------------------------------------------------- 1 | require 'swiftcore/Swiftiply/swiftiply_client' 2 | 3 | class SlowEchoClient < SwiftiplyClientProtocol 4 | 5 | def post_init 6 | @httpdata = '' 7 | @timer_set = false 8 | super 9 | end 10 | 11 | def receive_data data 12 | @httpdata << data 13 | 14 | unless @timer_set 15 | EventMachine.add_timer(2) {self.send_http_data(@httpdata); self.close_connection_after_writing} 16 | @timer_set = true 17 | end 18 | end 19 | end 20 | 21 | if ARGV[0] and ARGV[0].index(/:/) > 0 22 | h,p = ARGV[0].split(/:/,2) 23 | EventMachine.run { SlowEchoClient.connect(h,p.to_i) } 24 | else 25 | puts "slow_echo_client HOST:PORT" 26 | end 27 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/mongrel/swiftiplied_hello.rb: -------------------------------------------------------------------------------- 1 | begin 2 | load_attempted ||= false 3 | require 'swiftcore/swiftiplied_mongrel' 4 | rescue LoadError => e 5 | unless load_attempted 6 | load_attempted = true 7 | require 'rubygems' 8 | retry 9 | end 10 | raise e 11 | end 12 | 13 | class SimpleHandler < Mongrel::HttpHandler 14 | def process(request, response) 15 | response.start(200) do |head,out| 16 | head["Content-Type"] = "text/plain" 17 | out.write("hello!\n") 18 | end 19 | end 20 | end 21 | 22 | httpserver = Mongrel::HttpServer.new("127.0.0.1", 29999) 23 | httpserver.register("/hello", SimpleHandler.new) 24 | httpserver.register("/dir", Mongrel::DirHandler.new(".")) 25 | httpserver.run.join 26 | -------------------------------------------------------------------------------- /src/swiftcore/method_builder.rb: -------------------------------------------------------------------------------- 1 | # Mixin to allow dynamic construction of instance methods. 2 | 3 | module MethodBuilder 4 | def method_builder(method_name, method_args, chunks, callback_args = []) 5 | if Array === method_args 6 | pma = [] method_args.each do |ma| 7 | if Array === ma 8 | pma << "#{ma[0]}=#{ma[1]}" 9 | else 10 | pma << ma.to_s 11 | end 12 | end 13 | parsed_method_args = pma.join(',') 14 | else 15 | parsed_method_args = method_args.to_s 16 | end 17 | 18 | m = "def #{method_name}(#{parsed_method_args})\n" chunks.each do |chunk| 19 | if chunk.respond_to?(:call) 20 | m << chunk.call(*args) 21 | else 22 | m << chunk.to_s 23 | end 24 | end m << "\nend" 25 | 26 | class_eval m 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/bin/echo_client: -------------------------------------------------------------------------------- 1 | # This is a very simple, naive Swiftiply client. All that is does is echo whatever 2 | # request it receives as its response, in a very simplistic way. 3 | 4 | require 'swiftcore/Swiftiply/swiftiply_client' 5 | 6 | class EchoClient < SwiftiplyClientProtocol 7 | def post_init 8 | @httpdata = '' 9 | super 10 | end 11 | 12 | def receive_data data 13 | @httpdata << data 14 | if @httpdata =~ /\r\n\r\n/ 15 | send_http_data(@httpdata) 16 | @httpdata = '' 17 | end 18 | end 19 | end 20 | 21 | if ARGV[0] and ARGV[0].index(/:/) > 0 22 | h,p = ARGV[0].split(/:/,2) 23 | EventMachine.run { EchoClient.connect(h,p.to_i,ARGV[1] || '') } 24 | else 25 | puts "echo_client HOST:PORT [KEY]" 26 | end -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_serve_static_file_xsendfile/sendfile_client.rb: -------------------------------------------------------------------------------- 1 | # This client returns 2 | 3 | require 'swiftcore/Swiftiply/swiftiply_client' 4 | 5 | class SendFileClient < SwiftiplyClientProtocol 6 | def post_init 7 | @httpdata = '' 8 | super 9 | end 10 | 11 | def receive_data data 12 | @httpdata << data 13 | if @httpdata =~ /\r\n\r\n/ 14 | @httpdata =~ /^(\w+)\s+([^\s\?]+).*(1.\d)/ 15 | send_http_data("Doing X-Sendfile to #{$2}",{'Connection' => 'close', 'X-Sendfile' => $2},200,'OK') 16 | @httpdata = '' 17 | end 18 | end 19 | end 20 | 21 | if ARGV[0] and ARGV[0].index(/:/) > 0 22 | h,p = ARGV[0].split(/:/,2) 23 | EventMachine.run { SendFileClient.connect(h,p.to_i,ARGV[1] || '') } 24 | else 25 | puts "sendfile_client HOST:PORT [KEY]" 26 | end# -*- coding: ISO-8859-1 -*- 27 | 28 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/dynamic_request_cache.rb: -------------------------------------------------------------------------------- 1 | begin 2 | load_attempted ||= false 3 | require 'swiftcore/Swiftiply/cache_base' 4 | rescue LoadError => e 5 | if !load_attempted 6 | load_attempted = true 7 | begin 8 | require 'rubygems' 9 | rescue LoadError 10 | raise e 11 | end 12 | retry 13 | end 14 | raise e 15 | end 16 | 17 | module Swiftcore 18 | module Swiftiply 19 | class DynamicRequestCache < CacheBase 20 | attr_accessor :one_client_name 21 | 22 | def initialize(docroot, vw, ts, maxsize) 23 | @docroot = docroot 24 | super(vw,ts,maxsize) 25 | end 26 | 27 | def verify(path) 28 | if @docroot && self[path] 29 | if ProxyBag.find_static_file(@docroot,path,@one_client_name) 30 | self.delete path 31 | false 32 | else 33 | true 34 | end 35 | else 36 | false 37 | end 38 | end 39 | end 40 | end 41 | end -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_ssl/test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQCsR9GmSjSkPQ4CvB8MsYQGsbGzVoZ6hNINGZuwvV/gb2t0/7TZ 3 | Z7NLQvfCmf3k30ahq2D/zvlDoz3uDEjUlKvEV6z5uOne75CSdtha0sOpWDJ6m5PZ 4 | Cm8SpsU2zWUVN6K64+l7RZfJCAYAnvRl4ottaYsLIW1MGf9HC9xTq17SYwIDAQAB 5 | AoGATcjneaNLfVQrvURe6IZFzBfy2bwZX7wUcuG7D+ORJG5qIBtL9lUB+Ns1rmGE 6 | 5w+AfoI4e4dhuA4+afqV75Vor7Go6aNRZqMiqHM3E1NhghzRl7xU1VhaWdqOxYsu 7 | kFOvmnGkeoA/Ydkg2ZragqZyMyqIeLN5CFLeWm4CSPpZ3TECQQDg2n3qYwLzHOif 8 | eMYK5zPm+lzbRTkYJ58r/XebFypMqStfW7/yfH0AiKFWOCHwXbnRTJ8SBviq9Jtu 9 | OVuhWL+dAkEAxCULAWx0gjp/WIuhWyxD5ol89Cjjsfu2pH4jbxtPaZCz04jVbcWT 10 | jlgqAqOuk7abfC5n8Ub5cOMjhf6LwCY5/wJAXZbTvhFEEwi/UlEkrTkag1NF/wZL 11 | A2DKgbbYZ7c2pf3rzZ8Uv8tNBEHaVVa72Z5JT3KC6y/3pMB3SWOaXgfgiQJBAMBy 12 | C9tIuwNvO7T3wsf+pVxS91tjpwvhCXFZJZEEvaS4ygc5URbT7JOT4xwV1tqtJt7v 13 | dSJg3aqp4Re+CQXtO6cCQQCZ2vlEMtPmmpwpgAEhewVjXr45RTnxgK6OO93ezOj7 14 | OiZz9tBof/hnah5U6KrUo+arFl2EgkROyzteQN2HrpeX 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /test/TC_Swiftiply/test_ssl/test.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICfjCCAeegAwIBAgIBADANBgkqhkiG9w0BAQQFADCBhDELMAkGA1UEBhMCdXMx 3 | EDAOBgNVBAgTB1d5b21pbmcxFjAUBgNVBAoTDVN3aWZ0Y29yZS5vcmcxEDAOBgNV 4 | BAcTB1ZldGVyYW4xFjAUBgNVBAMTDXN3aWZ0Y29yZS5vcmcxITAfBgkqhkiG9w0B 5 | CQEWEnd5aGFpbmVzQGdtYWlsLmNvbTAeFw0wODAxMTIwMzM5MDVaFw0wOTAxMTEw 6 | MzM5MDVaMIGEMQswCQYDVQQGEwJ1czEQMA4GA1UECBMHV3lvbWluZzEWMBQGA1UE 7 | ChMNU3dpZnRjb3JlLm9yZzEQMA4GA1UEBxMHVmV0ZXJhbjEWMBQGA1UEAxMNc3dp 8 | ZnRjb3JlLm9yZzEhMB8GCSqGSIb3DQEJARYSd3loYWluZXNAZ21haWwuY29tMIGf 9 | MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsR9GmSjSkPQ4CvB8MsYQGsbGzVoZ6 10 | hNINGZuwvV/gb2t0/7TZZ7NLQvfCmf3k30ahq2D/zvlDoz3uDEjUlKvEV6z5uOne 11 | 75CSdtha0sOpWDJ6m5PZCm8SpsU2zWUVN6K64+l7RZfJCAYAnvRl4ottaYsLIW1M 12 | Gf9HC9xTq17SYwIDAQABMA0GCSqGSIb3DQEBBAUAA4GBABfV8gLt6BFfPWVvs2HW 13 | pg6fLsyeq3/l+Yhe/TSVVfoKyVbrZOCD3JG3C2VnkmgJxqyCpy1HHDjl57qLOCUu 14 | BxloVkK3ZpGpaOkfHpluYr+kvKML1PBt9UkyTbmDoTQTD2eMdLpj/z/fXjG5dZCw 15 | lSGr25W6yePTjYYK3clyJj9g 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /src/swiftcore/streamer.rb: -------------------------------------------------------------------------------- 1 | begin 2 | load_attempted ||= false 3 | require 'em/streamer' 4 | rescue LoadError 5 | unless load_attempted 6 | load_attempted = true 7 | require 'rubygems' 8 | retry 9 | end 10 | end 11 | 12 | module EventMachine 13 | class FileStreamer 14 | 15 | def stream_one_chunk 16 | loop { 17 | if @position < @size 18 | if @connection.get_outbound_data_size > BackpressureLevel 19 | EventMachine::next_tick {stream_one_chunk} 20 | break 21 | else 22 | len = @size - @position 23 | len = ChunkSize if (len > ChunkSize) 24 | 25 | #@connection.send_data( "#{format("%x",len)}\r\n" ) if @http_chunks 26 | if @http_chunks 27 | @connection.send_data( "#{len.to_s(16)}\r\n" ) if @http_chunks 28 | @connection.send_data( @mapping.get_chunk( @position, len )) 29 | @connection.send_data("\r\n") if @http_chunks 30 | else 31 | @connection.send_data( @mapping.get_chunk( @position, len )) 32 | end 33 | 34 | @position += len 35 | end 36 | else 37 | @connection.send_data "0\r\n\r\n" if @http_chunks 38 | @mapping.close 39 | succeed 40 | break 41 | end 42 | } 43 | end 44 | 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/file_cache.rb: -------------------------------------------------------------------------------- 1 | begin 2 | load_attempted ||= false 3 | require 'swiftcore/Swiftiply/cache_base' 4 | rescue LoadError => e 5 | if !load_attempted 6 | load_attempted = true 7 | begin 8 | require 'rubygems' 9 | rescue LoadError 10 | raise e 11 | end 12 | retry 13 | end 14 | raise e 15 | end 16 | 17 | module Swiftcore 18 | module Swiftiply 19 | class FileCache < CacheBase 20 | 21 | def add(path_info,path,data,etag,mtime,cookie,header) 22 | unless self[path_info] 23 | add_to_verification_queue(path_info) 24 | ProxyBag.log(owner_hash).log('info',"Adding file #{path} to file cache as #{path_info}") if ProxyBag.level(owner_hash) > 2 25 | end 26 | self[path_info] = [data,etag,mtime,path,cookie,header] 27 | end 28 | 29 | def verify(path_info) 30 | if f = self[path_info] 31 | if File.exist?(f[3]) and File.mtime(f[3]) == f[2] 32 | true 33 | else 34 | ProxyBag.log(owner_hash).log('info',"Removing file #{path_info} from file cache") if ProxyBag.level(owner_hash) > 2 35 | false 36 | end 37 | else 38 | # It was in the verification queue, but not in the file cache. 39 | false 40 | end 41 | end 42 | 43 | end 44 | end 45 | end 46 | 47 | -------------------------------------------------------------------------------- /src/swiftcore/hash.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | 3 | # Hash#merge doesn't work right if any of the values in the hash are themselves 4 | # hashes. This creates frowns on my normally smiling face. This method 5 | # will walk a hash, merging it, while preserving default_proc's that may 6 | # exist on the hashes involved. The code was adopted from IOWA. It's also 7 | # quite likely that it sucks. I wrote it years ago, and have left it alone 8 | # since then. If you need similar functionality in your code somewhere, 9 | # though, feel free to use this. If you make it better, I'd appreciate it 10 | # if you would send me your patches, though. 11 | 12 | def rmerge!(h) 13 | h.each do |k,v| 14 | if v.kind_of?(::Hash) 15 | if self[k].kind_of?(::Hash) 16 | unless self[k].respond_to?(:rmerge!) 17 | if dp = self[k].default_proc 18 | self[k] = Hash.new {|h,k| dp.call(h,k)}.rmerge!(self[k]) 19 | else 20 | osk = self[k] 21 | self[k] = Hash.new 22 | self[k].rmerge!(osk) 23 | end 24 | end 25 | self[k].rmerge!(v) 26 | else 27 | if self.default_proc 28 | self.delete k 29 | self[k] 30 | end 31 | unless self[k].kind_of?(::Hash) 32 | self.delete k 33 | self[k] = Hash.new 34 | end 35 | self[k].rmerge!(v) 36 | end 37 | else 38 | self[k] = v 39 | end 40 | end 41 | end 42 | 43 | end -------------------------------------------------------------------------------- /ext/fastfilereader/mapper.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | $Id: mapper.h 4529 2007-07-04 11:32:22Z francis $ 4 | 5 | File: mapper.h 6 | Date: 02Jul07 7 | 8 | Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved. 9 | Gmail: garbagecat10 10 | 11 | This program is free software; you can redistribute it and/or modify 12 | it under the terms of either: 1) the GNU General Public License 13 | as published by the Free Software Foundation; either version 2 of the 14 | License, or (at your option) any later version; or 2) Ruby's License. 15 | 16 | See the file COPYING for complete licensing information. 17 | 18 | *****************************************************************************/ 19 | 20 | 21 | #ifndef __Mapper__H_ 22 | #define __Mapper__H_ 23 | 24 | 25 | /************** 26 | class Mapper_t 27 | **************/ 28 | 29 | class Mapper_t 30 | { 31 | public: 32 | Mapper_t (const string&); 33 | virtual ~Mapper_t(); 34 | 35 | const char *GetChunk (unsigned); 36 | void Close(); 37 | size_t GetFileSize() {return FileSize;} 38 | 39 | private: 40 | size_t FileSize; 41 | 42 | #ifdef OS_UNIX 43 | private: 44 | int Fd; 45 | const char *MapPoint; 46 | #endif // OS_UNIX 47 | 48 | #ifdef OS_WIN32 49 | private: 50 | HANDLE hFile; 51 | HANDLE hMapping; 52 | const char *MapPoint; 53 | #endif // OS_WIN32 54 | 55 | }; 56 | 57 | 58 | #endif // __Mapper__H_ 59 | 60 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/content_response.rb: -------------------------------------------------------------------------------- 1 | require 'digest/md5' 2 | 3 | module Swiftcore 4 | module Swiftiply 5 | class ContentResponse < ::Hash 6 | # data,etag,mtime,path,cookies,headers 7 | def initialize(uri, headers, cacheable_data) 8 | parse_basic_http_data headers 9 | self[:uri] = uri 10 | self[:headers] = headers 11 | self[:data] = cacheable_data 12 | self[:ETag] ||= Digest::MD5.hexdigest(cacheable_data) 13 | end 14 | 15 | def parse_basic_http_data(headers) 16 | # The basic interesting pieces are ETag, Date, Expires, Vary, and Cache-Control. 17 | # I can hear you thinking right now that a line of 'if' statements is gross. 18 | # You're right. It's also a heaping hell of a lot faster than using something 19 | # like #scan. So, for this code, you'll just have to accept it. 20 | # Thanks for your consideration. 21 | if headers =~ /ETag:\s+(.*)/ 22 | self[:ETag] = $1 23 | end 24 | if headers =~ /Date:\s+(.*)/ 25 | self[:Date] = $1 26 | end 27 | if headers =~ /Expires:\s+(.*)/ 28 | self[:Expires] = $1 29 | end 30 | if headers =~ /Vary:\s+(.*)/ 31 | self[:Vary] = $1 32 | end 33 | if headers =~ /Cache-Control:\s+(.*)/ 34 | self[:Cache_Control] = $1 35 | end 36 | end 37 | 38 | def header(header_name) 39 | name_symbol = header_name.to_sym 40 | self[name_symbol] || (self[:headers] =~ /#{header_name}:\s+(.*)/ && self[name_symbol] = $1) 41 | end 42 | 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/cache_base.rb: -------------------------------------------------------------------------------- 1 | module Swiftcore 2 | begin 3 | # Attempt to load the SplayTreeMap and Deque. If it is not found, rubygems 4 | # will be required, and SplayTreeMap will be checked for again. If it is 5 | # still not found, then it will be recorded as unavailable, and the 6 | # remainder of the requires will be performed. If any of them are not 7 | # found, and rubygems has not been required, it will be required and the 8 | # code will retry, once. 9 | load_state ||= :start 10 | rubygems_loaded ||= false 11 | load_state = :splaytreemap 12 | require 'swiftcore/splaytreemap' unless const_defined?(:HasSplayTree) 13 | HasSplayTree = true unless const_defined?(:HasSplayTree) 14 | 15 | load_state = :deque 16 | require 'swiftcore/deque' unless const_defined?(:HasDeque) 17 | HasDeque = true unless const_defined?(:HasDeque) 18 | 19 | load_state = :remainder 20 | rescue LoadError => e 21 | if !rubygems_loaded 22 | begin 23 | require 'rubygems' 24 | rubygems_loaded = true 25 | rescue LoadError 26 | raise e 27 | end 28 | retry 29 | end 30 | 31 | case load_state 32 | when :deque 33 | HasDeque = false 34 | retry 35 | when :splaytreemap 36 | HasSplayTreeMap = false 37 | retry 38 | end 39 | 40 | raise e 41 | end 42 | 43 | 44 | if HasSplayTree 45 | require 'swiftcore/Swiftiply/splay_cache_base' 46 | else 47 | require 'swiftcore/Swiftiply/hash_cache_base' 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /setup.rb: -------------------------------------------------------------------------------- 1 | #!ruby 2 | 3 | basedir = File.dirname(__FILE__) 4 | $:.push(basedir) 5 | require 'external/package' 6 | require 'rbconfig' 7 | begin 8 | require 'rubygems' 9 | rescue LoadError 10 | end 11 | 12 | Dir.chdir(basedir) 13 | Package.setup("1.0") { 14 | # TODO pull version right from the code's version.rb. 15 | name "Swiftcore Swiftiply v. 0.6.5" 16 | 17 | build_ext "fastfilereader" 18 | translate(:ext, 'ext/fastfilereader/' => '/') 19 | #translate(:ext, 'ext/http11/' => 'iowa/') 20 | 21 | ext "ext/fastfilereader/fastfilereaderext.so" 22 | ext "ext/fastfilereader/fastfilereaderext.bundle" 23 | 24 | build_ext "deque" 25 | translate(:ext, 'ext/deque/' => '/swiftcore/') 26 | ext "ext/deque/deque.so" 27 | ext "ext/deque/deque.bundle" 28 | 29 | build_ext "splaytree" 30 | translate(:ext, 'ext/splaytree/' => '/swiftcore/') 31 | ext "ext/splaytree/splaytreemap.so" 32 | ext "ext/splaytree/splaytreemap.bundle" 33 | 34 | translate(:lib, 'src/' => '') 35 | translate(:bin, 'bin/' => '') 36 | lib(*Dir["src/swiftcore/**/*.rb"]) 37 | lib("src/swiftcore/evented_mongrel.rb") 38 | lib("src/swiftcore/swiftiplied_mongrel.rb") 39 | lib(*Dir["src/ramaze/adapter/*.rb"]) 40 | ri(*Dir["src/swiftcore/**/*.rb"]) 41 | bin "bin/swiftiply" 42 | bin "bin/swiftiply_mongrel_rails" 43 | #File.rename("#{Config::CONFIG["bindir"]}/mongrel_rails","#{Config::CONFIG["bindir"]}/mongrel_rails.orig") 44 | bin "bin/swiftiplied_mongrel_rails" 45 | bin "bin/evented_mongrel_rails" 46 | bin "bin/swiftiplyctl" 47 | 48 | unit_test "test/TC_ProxyBag.rb" 49 | unit_test "test/TC_Swiftiply.rb" 50 | unit_test "test/TC_Deque.rb" 51 | true 52 | } 53 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/control_protocol.rb: -------------------------------------------------------------------------------- 1 | require "swiftcore/Swiftiply/http_recognizer" 2 | 3 | module Swiftcore 4 | module Swiftiply 5 | 6 | # The ControlProtocol implements a simple REST HTTP handler that can be 7 | # used to affect the running configuration of Swiftiply. 8 | 9 | class ControlProtocol < HttpRecognizer 10 | 11 | # Should be able to use this to control all aspects of Swuftiply configuration and behavior. 12 | # - Query current performance information and statistics 13 | # * GET /status 14 | # * GET /status/DOMAIN 15 | # * GET /domains 16 | # * GET /config/DOMAIN 17 | # - Supply new config sections (json payload?) 18 | # * POST /config/DOMAIN 19 | # * PUT /config/DOMAIN 20 | # (There is currently no useful differentiation between the use of 21 | # POST and PUT in the API; they are both idempotent operations 22 | # that place the provided configuration into Swiftiply.) 23 | # - Remove config sections 24 | # * DELETE /config/DOMAIN 25 | def push 26 | case @request_method 27 | when CGET 28 | case @uri 29 | when /\/status(\/(.*))$/ 30 | when /\/domains/ 31 | when /\/config\/(.+)$/ 32 | end 33 | when CPOST, CPUT 34 | case @uri 35 | when /\/config\/(.+)$/ 36 | end 37 | when CDELETE 38 | case @uri 39 | when /\/config\/(.+)$/ 40 | end 41 | else 42 | # No supported method; discard 43 | close_connection 44 | end 45 | end 46 | 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/cache_base_mixin.rb: -------------------------------------------------------------------------------- 1 | module Swiftcore 2 | module Swiftiply 3 | module CacheBaseMixin 4 | attr_accessor :vw, :owners, :owner_hash, :name 5 | 6 | def add_to_verification_queue(path) 7 | @vq.unshift(path) 8 | true 9 | end 10 | 11 | def vqlength 12 | @vq.length 13 | end 14 | 15 | def check_verification_queue 16 | start = Time.now 17 | count = 0 18 | @push_to_vq = {} 19 | vql = @vq.length 20 | qg = vql - @old_vql 21 | while Time.now < start + @tl && !@vq.empty? 22 | count += 1 23 | path = @vq.pop 24 | verify(path) ? @push_to_vq[path] = 1 : delete(path) 25 | end 26 | @push_to_vq.each_key {|x| add_to_verification_queue(x)} 27 | @old_vql = @vq.length 28 | 29 | rt = Time.now - start 30 | 31 | # This algorithm is self adaptive based on the amount of work 32 | # completed in the time slice, and the amount of remaining work 33 | # in the queue. 34 | # (verification_window / (verification_queue_length / count)) * (real_time / time_limit) 35 | 36 | # If the queue growth in the last time period exceeded the count of items consumed this time, 37 | # use the ratio of the two to reduce the count number. This will result in a shorter period of 38 | # of time before the next check cycle. This lets the system stay on top of things when there 39 | # are bursts. 40 | if qg > count 41 | count *= count/qg 42 | end 43 | if vql == 0 44 | @vw / 2 45 | else 46 | wait_time = (@vwtl * count) / (vql * rt) 47 | wait_time < rt ? rt * 2.0 : wait_time > @vw ? @vw : wait_time 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/content_cache_entry.rb: -------------------------------------------------------------------------------- 1 | # This was inspired by Paul Sadauskas' Resourceful library: http://github.com/paul/resourceful 2 | module Swiftcore 3 | module Swiftiply 4 | class ContentCacheEntry < ::Hash 5 | 6 | # @param [Resourceful::Request] request 7 | # The request whose response we are storing in the cache. 8 | # @param response 9 | # The Response obhect to be stored. 10 | def initialize(request, response) 11 | super() 12 | self[:request_uri] = request.uri 13 | self[:request_time] = request.request_time 14 | self[:request_vary_headers] = select_request_headers(request, response) 15 | self[:response] = response 16 | end 17 | 18 | # Returns true if this entry may be used to fullfil the given request, 19 | # according to the vary headers. 20 | # 21 | # @param request 22 | # The request to do the lookup on. 23 | def valid_for?(request) 24 | request.uri == self[:request_uri] && self[:request_vary_headers].all? {|key, value| request.header[key] == value } 25 | end 26 | 27 | # Selects the headers from the request named by the response's Vary header 28 | # http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.6 29 | # 30 | # @param [Resourceful::Request] request 31 | # The request used to obtain the response. 32 | # @param [Resourceful::Response] response 33 | # The response obtained from the request. 34 | def select_request_headers(request, response) 35 | headers = {} 36 | 37 | # Broken 38 | self[:response].header['Vary'].each { |name| header[name] = request.header[name] if request.header[name] } if response.header['Vary'] 39 | 40 | header 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /swiftiply.gemspec: -------------------------------------------------------------------------------- 1 | ##### 2 | # Swiftcore Swiftiply 3 | # http://swiftiply.swiftcore.org 4 | # Copyright 2007-2017 Kirk Haines 5 | # wyhaines@gmail.com 6 | # 7 | # Licensed under the Ruby License. See the README for details. 8 | # 9 | ##### 10 | lib = File.expand_path('../src', __FILE__) 11 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 12 | require 'swiftcore/Swiftiply/version' 13 | 14 | spec = Gem::Specification.new do |s| 15 | s.name = 'swiftiply' 16 | s.author = %q(Kirk Haines) 17 | s.email = %q(wyhaines@gmail.com) 18 | s.version = Swiftcore::Swiftiply::VERSION 19 | s.summary = %q(A fast clustering proxy for web applications.) 20 | s.platform = Gem::Platform::RUBY 21 | 22 | s.has_rdoc = true 23 | s.rdoc_options = %w(--title Swiftcore::Swiftiply --main README.md --line-numbers) 24 | s.extra_rdoc_files = %w(README.md) 25 | s.extensions << 'ext/fastfilereader/extconf.rb' 26 | s.extensions << 'ext/deque/extconf.rb' 27 | s.extensions << 'ext/splaytree/extconf.rb' 28 | s.files = Dir['**/*'] 29 | s.executables = %w(swiftiply swiftiplied_mongrel_rails evented_mongrel_rails swiftiply_mongrel_rails) 30 | s.require_paths = %w(src) 31 | 32 | s.requirements << "Eventmachine 0.8.1 or higher." 33 | s.add_dependency('eventmachine','>= 0.8.1') 34 | s.test_files = [] 35 | 36 | s.rubyforge_project = %q(swiftiply) 37 | s.homepage = %q(http://swiftiply.swiftcore.org/) 38 | description = [] 39 | File.open("README.md") do |file| 40 | file.each do |line| 41 | line.chomp! 42 | break if line.empty? 43 | description << "#{line.gsub(/\[\d\]/, '')}" 44 | end 45 | end 46 | s.description = description[1..-1].join(" ") 47 | end 48 | -------------------------------------------------------------------------------- /external/test_support.rb: -------------------------------------------------------------------------------- 1 | # A support module for the test suite. This provides a win32 aware 2 | # mechanism for doing fork/exec operations. It requires win32/process 3 | # to be installed, however. 4 | # 5 | module SwiftcoreTestSupport 6 | @run_modes = [] 7 | 8 | def self.create_process(args) 9 | @fork_ok = true unless @fork_ok == false 10 | pid = nil 11 | begin 12 | raise NotImplementedError unless @fork_ok 13 | unless pid = fork 14 | Dir.chdir args[:dir] 15 | exec(*args[:cmd]) 16 | end 17 | rescue NotImplementedError 18 | @fork_ok = false 19 | begin 20 | require 'rubygems' 21 | rescue LoadError 22 | end 23 | 24 | begin 25 | require 'win32/process' 26 | rescue LoadError 27 | raise "Please install win32-process to run all tests on a Win32 platform. 'gem install win32-process' or http://rubyforge.org/projects/win32utils" 28 | end 29 | cwd = Dir.pwd 30 | Dir.chdir args[:dir] 31 | pid = Process.create(:app_name => args[:cmd].join(' ')) 32 | Dir.chdir cwd 33 | end 34 | pid 35 | end 36 | 37 | def self.test_dir(dir) 38 | File.dirname(File.expand_path(dir)) 39 | end 40 | 41 | def self.cd_to_test_dir(dir) 42 | Dir.chdir(File.dirname(File.expand_path(dir))) 43 | end 44 | 45 | def self.set_src_dir 46 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'../src')) 47 | end 48 | 49 | @announcements = {} 50 | def self.announce(section,msg) 51 | unless @announcements.has_key?(section) 52 | puts "\n\n" 53 | puts msg,"#{'=' * msg.length}\n\n" 54 | @announcements[section] = true 55 | end 56 | end 57 | 58 | end 59 | 60 | class EMConnectionMock 61 | attr_accessor :uri, :name 62 | 63 | def send_data data 64 | end 65 | 66 | def send_file_data filename 67 | end 68 | 69 | def close_connection; end 70 | def close_connection_after_writing; end 71 | end -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/proxy_backends/traditional/static_directory.rb: -------------------------------------------------------------------------------- 1 | # Config looks for a list of backends which are defined statically and puts them into a queue. 2 | module Swiftcore 3 | module Swiftiply 4 | module Proxies 5 | class TraditionalStaticDirectory 6 | # servers: 7 | # - http://site.com:port/url 8 | # - http://site2.com:port2/url2 9 | def self.config(conf,new_config) 10 | @queue = ::Swiftcore.const_defined?(:Deque) ? Swiftcore::Deque.new : [] 11 | servers = conf[Cservers] 12 | if Array === servers 13 | servers.each do |server| 14 | queue.push server 15 | end 16 | elsif servers 17 | queue.push servers 18 | end 19 | end 20 | 21 | def self.queue 22 | @queue 23 | end 24 | 25 | def self.backend_class 26 | @backend_class 27 | end 28 | 29 | def self.backend_class=(val) 30 | @backend_class = val 31 | end 32 | 33 | def initialize(*args) 34 | @queue = self.class.queue 35 | @backend_class = self.class.backend_class 36 | end 37 | 38 | # The queue is circular. Any element that is popped off the end is shifted back onto the front and vice versa. 39 | def pop 40 | server = @queue.pop 41 | host, port = server.split(C_colon,2) 42 | @queue.unshift server 43 | host ||= C_localhost 44 | port ||= C80 45 | EventMachine.connect(host,port,@backend_class) 46 | rescue Exception # In an ideal world, we do something useful with regard to logging/reporting this exception. 47 | false 48 | end 49 | 50 | def unshift(val) 51 | @queue.unshift val 52 | end 53 | 54 | def push(val) 55 | @queue.push val 56 | end 57 | 58 | def delete(val) 59 | @queue.delete val 60 | end 61 | 62 | def requeue(*args); end 63 | 64 | def status 65 | end 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /bin/swiftiply_mongrel_rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV['SWIFT'] = 'true' 4 | require 'optparse' 5 | 6 | class MongrelSwiftStart 7 | 8 | def self.parse_options(config = {}) 9 | defaults={:env => 'production', 10 | :pid => (File.expand_path(Dir.pwd)+'/log'), 11 | :host => '127.0.0.1', 12 | :port => 4000, 13 | :num => 1 14 | } 15 | OptionParser.new do |opts| 16 | opts.banner = 'Usage: mongrel_swift [options]' 17 | opts.separator '' 18 | opts.on('-C','--config CONFFILE',"The mongrel configuration file to read.") do |conf| 19 | config[:conf] = conf 20 | end 21 | opts.on('-h','--host [HOST]','The hostname/IP address that the swiftiply proxy will listen on for backend connections.') do |host| 22 | config[:host] = host 23 | end 24 | opts.on('-p','--port [PORT]','The port that swiftiply proxy is listening on for backend connections. Use the same port for all mongrels!') do |port| 25 | config[:port] = port 26 | end 27 | opts.on('-d','--daemonize','Whether mongrel_rails should put itself into the background.') do |yn| 28 | config[:daemonize] = true 29 | end 30 | opts.on('-n','--num-mongrels [NUM]','The number of mongrels to start.') do |numm| 31 | config[:num] = numm.to_i 32 | end 33 | opts.on('-P','--pidfiles [NUM]','Path to store PID files.') do |pid| 34 | config[:pid] = pid.to_i 35 | end 36 | end.parse! 37 | @config = defaults.update(config) 38 | end 39 | 40 | def self.run 41 | parse_options 42 | @config[:num].times do |i| 43 | cmd = "mongrel_rails start -p #{@config[:port]} " << 44 | "-e #{@config[:env]} #{@config[:daemonize] ? '-d' : ''}" << 45 | "-P #{File.join(@config[:pid],'dog' + i.to_s + '.pid')}" << 46 | "#{@config[:conf] ? ' -C '+@config[:conf] : ''}" 47 | output = `#{cmd}` 48 | puts output 49 | end 50 | end 51 | 52 | end 53 | 54 | MongrelSwiftStart.run 55 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/etag_cache.rb: -------------------------------------------------------------------------------- 1 | begin 2 | load_attempted ||= false 3 | require 'swiftcore/Swiftiply/cache_base' 4 | rescue LoadError => e 5 | if !load_attempted 6 | load_attempted = true 7 | begin 8 | require 'rubygems' 9 | rescue LoadError 10 | raise e 11 | end 12 | retry 13 | end 14 | raise e 15 | end 16 | 17 | module Swiftcore 18 | module Swiftiply 19 | 20 | ReadMode = 'r:ASCII-8BIT'.freeze 21 | 22 | class EtagCache < CacheBase 23 | 24 | def etag_mtime(path) 25 | self[path] || self[path] = self.calculate_etag(path) 26 | end 27 | 28 | def etag(path) 29 | self[path] && self[path].first || (self[path] = self.calculate_etag(path)).first 30 | end 31 | 32 | def mtime(path) 33 | self[path] && self[path].last || (self[path] = self.calculate_etag(path)).last 34 | end 35 | 36 | def verify(path) 37 | if et = self[path] and File.exist?(path) 38 | mt = File.mtime(path) 39 | if mt == et.last 40 | true 41 | else 42 | (self[path] = self.calculate_etag(path)).first 43 | end 44 | else 45 | false 46 | end 47 | end 48 | 49 | def calculate_etag(path) 50 | stats = File.stat(path) 51 | mtime = stats.mtime 52 | # Making an etag off of modification time + size is a lot faster than 53 | # calculating a hash for the whole file, and is adequate for the 54 | # purposes of an etag. 55 | etag = "#{mtime.to_i.to_s(16)}-#{stats.size.to_s(16)}" 56 | 57 | unless self[path] 58 | add_to_verification_queue(path) 59 | ProxyBag.log(owner_hash).log('info',"Adding ETag #{etag} for #{path} to ETag cache") if ProxyBag.level(owner_hash) > 2 60 | end 61 | [etag,mtime] 62 | rescue Exception 63 | # Pulling file stats failed; probably a race condition and file was deleted, but....there should be checks. 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/support_pagecache.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: ISO-8859-1 -*- 2 | 3 | # support_pagecache will be required by Swiftiply, as necessary, in order to 4 | # enable support for Rails style page caching. The caching implementation is 5 | # flexible, using /public/ as the cache directory by default, but allowing that 6 | # default to be overridden, and looking for .html files by default, but also 7 | # allowing that to be overridden. 8 | 9 | module Swiftcore 10 | module Swiftiply 11 | class ProxyBag 12 | DefaultCacheDir = 'public'.freeze 13 | DefaultSuffixes = ['html'.freeze] 14 | @suffix_list = {} 15 | @cache_dir = {} 16 | 17 | class << self 18 | def add_suffix_list(list,name) 19 | @suffix_list[name] = list 20 | end 21 | 22 | def remove_suffix_list(list,name) 23 | @suffix_list.delete name 24 | end 25 | 26 | def add_cache_dir(dir,name) 27 | @cache_dir[name] = dir 28 | end 29 | 30 | def remove_cache_dir(name) 31 | @cache_dir.delete name 32 | end 33 | 34 | def find_static_file(dr,path_info,client_name) 35 | path = File.join(dr,path_info) 36 | puts path 37 | if FileTest.exist?(path) and FileTest.file?(path) and File.expand_path(path).index(dr) == 0 and !(x = static_mask(client_name) and path =~ x) 38 | path 39 | elsif @suffix_list.has_key?(client_name) 40 | path = File.join(dr,@cache_dir[client_name],path_info) 41 | if FileTest.exist?(path) and FileTest.file?(path) and File.expand_path(path).index(dr) == 0 and !(x = static_mask(client_name) and path =~ x) 42 | path 43 | else 44 | for suffix in @suffix_list[client_name] do 45 | p = "#{path}.#{suffix}" 46 | if FileTest.exist?(p) and FileTest.file?(p) and File.expand_path(p).index(dr) == 0 and !(x = static_mask(client_name) and p =~ x) 47 | return p 48 | end 49 | end 50 | nil 51 | end 52 | else 53 | nil 54 | end 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/swiftiply_client.rb: -------------------------------------------------------------------------------- 1 | # Encoding:ascii-8bit 2 | 3 | begin 4 | load_attempted ||= false 5 | require 'eventmachine' 6 | require 'socket' 7 | rescue LoadError => e 8 | unless load_attempted 9 | load_attempted = true 10 | # Ugh. Everything gets slower once rubygems are used. So, for the 11 | # best speed possible, don't install EventMachine or Swiftiply via 12 | # gems. 13 | require 'rubygems' 14 | retry 15 | end 16 | raise e 17 | end 18 | 19 | # This is a basic Swiftiply client implementation. 20 | 21 | class SwiftiplyClientProtocol < EventMachine::Connection 22 | 23 | attr_accessor :hostname, :port, :key, :ip, :id 24 | 25 | C_dotdotdot = '...'.freeze 26 | C0s = [0,0,0,0].freeze 27 | CCCCC = 'CCCC'.freeze 28 | CContentLength = 'Content-Length'.freeze 29 | DefaultHeaders = {'Connection' => 'close', 'Content-Type' => 'text/plain'} 30 | 31 | def self.connect(hostname = nil,port = nil,key = '') 32 | key = key.to_s 33 | 34 | connection = ::EventMachine.connect(hostname, port, self) do |conn| 35 | conn.hostname = hostname 36 | conn.port = port 37 | conn.key = key 38 | ip = conn.ip = conn.__get_ip(hostname) 39 | #conn.id = 'swiftclient' << ip.collect {|x| sprintf('%02x',x.to_i)}.join << sprintf('%04x',port.to_i)<< sprintf('%02x',key.length) << key 40 | conn.id = 'swiftclient' << ip.collect {|x| sprintf('%02x',x.to_i)}.join << sprintf('%04x',$$)<< sprintf('%02x',key.length) << key 41 | conn.set_comm_inactivity_timeout inactivity_timeout 42 | end 43 | end 44 | 45 | def self.inactivity_timeout 46 | @inactivity_timeout || 60 47 | end 48 | 49 | def self.inactivity_timeout=(val) 50 | @inactivity_timeout = val 51 | end 52 | 53 | def connection_completed 54 | send_data @id 55 | end 56 | 57 | def unbind 58 | ::EventMachine.add_timer(rand(2)) {self.class.connect(@hostname,@port,@key)} 59 | end 60 | 61 | def send_http_data(data,h = {},status = 200, msg = C_dotdotdot) 62 | headers = DefaultHeaders.merge(h) 63 | headers[CContentLength] = data.length 64 | header_string = '' 65 | headers.each {|k,v| header_string << "#{k}: #{v}\r\n"} 66 | send_data("HTTP/1.1 #{status} #{msg}\r\n#{header_string}\r\n#{data}") 67 | end 68 | 69 | def __get_ip hostname 70 | Socket.gethostbyname(hostname)[3].unpack(CCCCC) rescue ip = C0s 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/proxy_backends/traditional/redis_directory.rb: -------------------------------------------------------------------------------- 1 | require 'redis' 2 | 3 | # Lookup directory information from redis repo. 4 | module Swiftcore 5 | module Swiftiply 6 | module Proxies 7 | class TraditionalRedisDirectory 8 | # servers: 9 | # host: HOSTNAME (defaults to 127.0.0.1) 10 | # port: PORT (defaults to 6379) 11 | # db: Redis DB (defaults to 0) 12 | # password: (defaults to none) 13 | 14 | def self.config(conf,new_config) 15 | redis_config = {} 16 | (conf[Cservers] || {}).each {|k,v| redis_config[k.intern] = v} 17 | @redis = Redis.new(redis_config) 18 | rescue Exception => e 19 | puts "Failed to connect to the Redis server using these parameters: #{redis_config.to_yaml}" 20 | raise e 21 | end 22 | 23 | def self.redis 24 | @redis 25 | end 26 | 27 | def self.backend_class 28 | @backend_class 29 | end 30 | 31 | def self.backend_class=(val) 32 | @backend_class = val 33 | end 34 | 35 | def initialize(*args) 36 | @redis = self.class.redis 37 | @backend_class = self.class.backend_class 38 | end 39 | 40 | def pop 41 | key = ProxyBag.current_client_name 42 | data = @redis.rpoplpush(key,"#{key}.inuse") 43 | if data 44 | host, port = data.split(C_colon,2) 45 | host ||= C_localhost 46 | port ||= C80 47 | EventMachine.connect(host,port,@backend_class,host,port) 48 | else 49 | false 50 | end 51 | rescue Exception => e 52 | false 53 | end 54 | 55 | def unshift(val);end 56 | 57 | def push(val);end 58 | 59 | def delete(val);end 60 | 61 | def requeue(key, host, port) 62 | hp = "#{host}:#{port}" 63 | @redis.lrem("#{key}.inuse", 1, hp) 64 | @redis.lpush(key, hp) 65 | rescue Exception => e 66 | # Use an EM timer to reschedule the requeue just a very short time into the future? 67 | # The only time this will occur is if the redis server goes away. 68 | end 69 | 70 | def status 71 | r = '' 72 | keys = @redis.keys('*') 73 | r << "#{@redis.dbsize} -- #{keys.to_yaml}" 74 | keys.each do |k| 75 | r << " #{k}(#{@redis.llen(k)})\n #{@redis.lrange(k,0,@redis.llen(k)).to_yaml}" 76 | end 77 | r 78 | rescue Exception => e 79 | r << self.inspect 80 | r << e 81 | r << e.backtrace.to_yaml 82 | r 83 | end 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/cluster_protocol.rb: -------------------------------------------------------------------------------- 1 | # Encoding:ascii-8bit 2 | 3 | require "swiftcore/Swiftiply/http_recognizer" 4 | 5 | module Swiftcore 6 | module Swiftiply 7 | 8 | # The ClusterProtocol is the subclass of EventMachine::Connection used 9 | # to communicate between Swiftiply and the web browser clients. 10 | 11 | class ClusterProtocol < HttpRecognizer 12 | 13 | proxy_bag_class_is Swiftcore::Swiftiply::ProxyBag 14 | 15 | C503Header = "HTTP/1.1 503 Server Unavailable\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n" 16 | 17 | def self.init_class_variables 18 | @count_503 = 0 19 | super 20 | end 21 | 22 | def self.increment_503_count 23 | @count_503 += 1 24 | end 25 | 26 | # Hardcoded 503 response that is sent if a connection is timed out while 27 | # waiting for a backend to handle it. 28 | 29 | def send_503_response 30 | ip = Socket::unpack_sockaddr_in(get_peername).last rescue Cunknown_host 31 | error = "The request (#{@uri} --> #{@name}), received on #{create_time.asctime} from #{ip} timed out before being deployed to a server for processing." 32 | send_data "#{C503Header}Server Unavailable\n\n#{error}" 33 | ProxyBag.logger.log(Cinfo,"Server Unavailable -- #{error}") 34 | close_connection_after_writing 35 | increment_503_count 36 | end 37 | 38 | def increment_503_count 39 | @klass.increment_503_count 40 | end 41 | 42 | def send_healthcheck_response 43 | ip = Socket::unpack_sockaddr_in(get_peername).last rescue Cunknown_host 44 | message = "Health request from #{ip} at #{ProxyBag.now.asctime}\n400:#{@count_400}\n404:#{@count_404}\n503:#{@count_503}\n" 45 | send_data "#{C200Header}#{message}" 46 | ProxyBag.logger.log(Cinfo,"healthcheck from ##{ip}") 47 | close_connection_after_writing 48 | end 49 | 50 | def push 51 | if @associate 52 | unless @redeployable 53 | # normal data push 54 | data = nil 55 | @associate.send_data data while data = @data.pop 56 | else 57 | # redeployable data push; just send the stuff that has 58 | # not already been sent. 59 | (@data.length - 1 - @data_pos).downto(0) do |p| 60 | d = @data[p] 61 | @associate.send_data d 62 | @data_len += d.length 63 | end 64 | @data_pos = @data.length 65 | 66 | # If the request size crosses the size limit, then 67 | # disallow redeployent of this request. 68 | if @data_len > @redeployable 69 | @redeployable = false 70 | @data.clear 71 | end 72 | end 73 | end 74 | end 75 | 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swiftiply v1.0.0 (http://github.com/wyhaines/swiftiply) 2 | 3 | Swiftiply is a backend agnostic clustering proxy for web applications that is 4 | specifically designed to support HTTP traffic from web frameworks. It is a targeted 5 | proxy, intended specifically for use in front of web frameworks, and is not a 6 | general purpose proxy. 7 | 8 | What it is, though, is a very fast, narrowly targeted clustering proxy, with the 9 | current implementation being written in Ruby. 10 | 11 | Swiftiply works differently from a traditional proxy. In Swiftiply, the 12 | backend processes are clients of the Swiftiply server -- they make persistent 13 | socket connections to Swiftiply. One of the major advantages to this 14 | architecture is that it allows one to start or stop backend processes at will, 15 | with no configuration of the proxy. The proxy always knows exactly what resources 16 | it has available to handle a given request. The obvious disadvantage is that this is 17 | not behavior that web applications typically expect. 18 | 19 | Swiftiply was originally written in an era when Mongrel was the preferred deployment 20 | method for most Ruby frameworks. Swiftiply includes a version of Mongrel(found in 21 | swiftcore/swiftiplied_mongrel.rb) that has been modified to work as a swiftiply client. 22 | This should be transparent to any existing Mongrel handlers, allowing them all to with 23 | Swiftiply. 24 | 25 | Swiftiply also provides a traditional proxy model, allowing it to be used as a proxy 26 | in front of any web application. 27 | 28 | TODO: Provide an implementation of a swiftiply access proxy. This is a Swiftiply 29 | TODO: "client" that maintains N connections into Swiftiply, but that operates as 30 | TODO: a traditional proxy on the web application facing side. This lets an individual 31 | TODO: server modulate the total number of connections that it is willing to handle 32 | TODO: simultaneously, while not requiring the applications themselves to know anything 33 | TODO: about it. 34 | 35 | CONFIGURATION 36 | 37 | Swiftiply takes a single configuration file which defines for it where it 38 | should listen for incoming connections, whether it should daemonize itself, 39 | and then provides a map of incoming domain names and the address/port to 40 | proxy that traffic to. That outgoing address/port is where the backends for 41 | that site will connect to. 42 | 43 | Here's an example: 44 | 45 | cluster_address: swiftcore.org 46 | cluster_port: 80 47 | daemonize: true 48 | map: 49 | - incoming: 50 | - swiftcore.org 51 | - www.swiftcore.org 52 | outgoing: 127.0.0.1:30000 53 | default: true 54 | - incoming: iowa.swiftcore.org 55 | outgoing: 127.0.0.1:30010 56 | - incoming: analogger.swiftcore.org 57 | outgoing: 127.0.0.1:30020 58 | - incoming: 59 | - swiftiply.com 60 | - www.swiftiply.com 61 | - swiftiply.swiftcore.org 62 | outgoing: 127.0.0.1:30030 63 | -------------------------------------------------------------------------------- /src/fastfilereader.rb: -------------------------------------------------------------------------------- 1 | # Written by Francis Cianfrocca (garbagecat10@gmail.com) with contributions 2 | # from Kirk Haines (wyhaines@gmail.com). 3 | 4 | begin 5 | load_attempted ||= false 6 | require 'eventmachine' 7 | require 'fastfilereaderext' 8 | rescue LoadError => e 9 | unless load_attempted 10 | load_attempted = true 11 | require 'eventmachine' 12 | require 'fastfilereaderext' 13 | retry 14 | end 15 | raise e 16 | end 17 | 18 | module EventMachine 19 | class FastFileReader 20 | include EventMachine::Deferrable 21 | 22 | attr_reader :size 23 | 24 | # TODO, make these constants tunable parameters 25 | ChunkSize = 16384 26 | BackpressureLevel = 50000 27 | MappingThreshold = 32768 28 | Crn = "\r\n".freeze 29 | C0rnrn = "0\r\n\r\n".freeze 30 | 31 | class << self 32 | # Return a newly-created instance of this class, or nil on error. 33 | # 34 | def open filename 35 | FastFileReader.new(filename) 36 | rescue 37 | nil 38 | end 39 | 40 | end 41 | 42 | # This constructor can throw exceptions. Use #open to avoid that fate. 43 | # 44 | def initialize filename 45 | # Throw an exception if we can't open the file. 46 | # TODO, perhaps we should throw a different exception? 47 | raise "no file" unless File.exist?(filename) 48 | 49 | @size = File.size?(filename) 50 | if @size >= MappingThreshold 51 | @mapping = Mapper.new( filename ) 52 | else 53 | @content = File.read( filename ) 54 | end 55 | end 56 | 57 | 58 | # This is a no-op for small files that have a @content 59 | # member. For large files with a @mapping, we call #close 60 | # on the mapping. In general, this will be done by the 61 | # finalizer when the GC runs, but there will be cases 62 | # when we will want to know that the underlying file mapping 63 | # is closed. This matters particularly on Windows, because 64 | # we're holding some HANDLE objects open that can cause 65 | # trouble for Ruby. 66 | def close 67 | @mapping.close if @mapping 68 | end 69 | 70 | # We expect to receive something like an EventMachine::Connection object. 71 | # We also expect to be running inside a reaactor loop, because we call 72 | # EventMachine#next_tick when we have too much data to send. 73 | def stream_as_http_chunks sink 74 | if @content 75 | if @content.length > 0 76 | sink.send_data( "#{@content.length.to_s(16)}\r\n#{@content}#{Crn}" ) 77 | end 78 | sink.send_data( C0rnrn ) 79 | set_deferred_success 80 | else 81 | @position = 0 82 | @sink = sink 83 | stream_one_http_chunk 84 | end 85 | end 86 | 87 | def stream_one_http_chunk 88 | loop { 89 | if @position < @size 90 | if @sink.get_outbound_data_size > BackpressureLevel 91 | EventMachine::next_tick {stream_one_http_chunk} 92 | break 93 | else 94 | len = @size - @position 95 | len = ChunkSize if (len > ChunkSize) 96 | @sink.send_data( "#{len.to_s(16)}\r\n#{@mapping.get_chunk( @position, len))}\r\n" ) 97 | @position += len 98 | end 99 | else 100 | @sink.send_data( C0rnrn ) 101 | set_deferred_success 102 | break 103 | end 104 | } 105 | end 106 | private :stream_one_http_chunk 107 | end 108 | end 109 | 110 | -------------------------------------------------------------------------------- /test/TC_Deque.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'external/test_support' 3 | SwiftcoreTestSupport.set_src_dir 4 | require 'rbconfig' 5 | require 'swiftcore/deque' 6 | 7 | class TC_Deque < Minitest::Test 8 | @@testdir = SwiftcoreTestSupport.test_dir(__FILE__) 9 | 10 | def setup 11 | Dir.chdir(@@testdir) 12 | SwiftcoreTestSupport.announce(:proxybag,"ProxyBag") 13 | end 14 | 15 | def teardown 16 | GC.start 17 | end 18 | 19 | def test_a_new 20 | dq = nil 21 | dq = Swiftcore::Deque.new 22 | assert_kind_of(Swiftcore::Deque,dq) 23 | end 24 | 25 | def test_b_unshift 26 | dq = Swiftcore::Deque.new 27 | 28 | dq.unshift "a" 29 | dq.unshift "b" 30 | dq.unshift "c" 31 | 32 | assert_equal('["c","b","a"]',dq.inspect) 33 | end 34 | 35 | def test_c_shift 36 | dq = Swiftcore::Deque.new 37 | dq.unshift "a" 38 | dq.unshift "b" 39 | dq.unshift "c" 40 | assert_equal("c",dq.shift) 41 | assert_equal("b",dq.shift) 42 | assert_equal("a",dq.shift) 43 | assert_equal(nil,dq.shift) 44 | end 45 | 46 | def test_d_push 47 | dq = Swiftcore::Deque.new 48 | dq.push "a" 49 | dq.push "b" 50 | dq.push "c" 51 | 52 | assert_equal('["a","b","c"]',dq.inspect) 53 | end 54 | 55 | def test_e_pop 56 | dq = Swiftcore::Deque.new 57 | dq.push "a" 58 | dq.push "b" 59 | dq.push "c" 60 | assert_equal("c",dq.pop) 61 | assert_equal("b",dq.pop) 62 | assert_equal("a",dq.pop) 63 | assert_equal(nil,dq.pop) 64 | end 65 | 66 | def test_f_size 67 | dq = Swiftcore::Deque.new 68 | dq.push "a" 69 | dq.push "b" 70 | dq.push "c" 71 | assert_equal(3,dq.size) 72 | end 73 | 74 | def test_g_max_size 75 | dq = Swiftcore::Deque.new 76 | dq.max_size 77 | end 78 | 79 | def test_h_clear 80 | dq = Swiftcore::Deque.new 81 | dq.push "a" 82 | dq.push "b" 83 | dq.push "c" 84 | dq.clear 85 | assert_equal(0,dq.size) 86 | assert_equal("[]",dq.inspect) 87 | end 88 | 89 | def test_i_empty 90 | dq = Swiftcore::Deque.new 91 | dq.push "a" 92 | assert(!dq.empty?) 93 | dq.clear 94 | assert(dq.empty?) 95 | end 96 | 97 | def test_j_to_s 98 | dq = Swiftcore::Deque.new 99 | dq.push "a" 100 | dq.push "b" 101 | dq.push "c" 102 | assert_equal("abc",dq.to_s) 103 | end 104 | 105 | def test_k_to_a 106 | dq = Swiftcore::Deque.new 107 | dq.push "a" 108 | dq.push "b" 109 | dq.push "c" 110 | assert_equal(["a","b","c"],dq.to_a) 111 | end 112 | 113 | def test_l_first 114 | dq = Swiftcore::Deque.new 115 | dq.push "a" 116 | dq.push "b" 117 | dq.push "c" 118 | assert_equal("a",dq.first) 119 | end 120 | 121 | def test_m_last 122 | dq = Swiftcore::Deque.new 123 | dq.push "a" 124 | dq.push "b" 125 | dq.push "c" 126 | assert_equal("c",dq.last) 127 | end 128 | 129 | def test_n_at 130 | dq = Swiftcore::Deque.new 131 | dq.push "a" 132 | dq.push "b" 133 | dq.push "c" 134 | assert_equal("a",dq.at(0)) 135 | assert_equal("a",dq[0]) 136 | assert_equal("b",dq.at(1)) 137 | assert_equal("b",dq[1]) 138 | assert_equal("c",dq.at(2)) 139 | assert_equal("c",dq[2]) 140 | end 141 | 142 | def test_o_index 143 | dq = Swiftcore::Deque.new 144 | dq.push "a" 145 | dq.push :b 146 | dq.push 37 147 | assert_equal(0,dq.index("a")) 148 | assert_equal(1,dq.index(:b)) 149 | assert_equal(2,dq.index(37)) 150 | end 151 | 152 | end 153 | -------------------------------------------------------------------------------- /ext/fastfilereader/rubymain.cpp: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | $Id: rubymain.cpp 4529 2007-07-04 11:32:22Z francis $ 4 | 5 | File: rubymain.cpp 6 | Date: 02Jul07 7 | 8 | Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved. 9 | Gmail: garbagecat10 10 | 11 | This program is free software; you can redistribute it and/or modify 12 | it under the terms of either: 1) the GNU General Public License 13 | as published by the Free Software Foundation; either version 2 of the 14 | License, or (at your option) any later version; or 2) Ruby's License. 15 | 16 | See the file COPYING for complete licensing information. 17 | 18 | *****************************************************************************/ 19 | 20 | 21 | 22 | #include 23 | #include 24 | using namespace std; 25 | 26 | #include 27 | #include "mapper.h" 28 | 29 | static VALUE EmModule; 30 | static VALUE FastFileReader; 31 | static VALUE Mapper; 32 | 33 | 34 | 35 | /********* 36 | mapper_dt 37 | *********/ 38 | 39 | static void mapper_dt (void *ptr) 40 | { 41 | if (ptr) 42 | delete (Mapper_t*) ptr; 43 | } 44 | 45 | /********** 46 | mapper_new 47 | **********/ 48 | 49 | static VALUE mapper_new (VALUE self, VALUE filename) 50 | { 51 | Mapper_t *m = new Mapper_t (StringValuePtr (filename)); 52 | if (!m) 53 | rb_raise (rb_eException, "No Mapper Object"); 54 | VALUE v = Data_Wrap_Struct (Mapper, 0, mapper_dt, (void*)m); 55 | return v; 56 | } 57 | 58 | 59 | /**************** 60 | mapper_get_chunk 61 | ****************/ 62 | 63 | static VALUE mapper_get_chunk (VALUE self, VALUE start, VALUE length) 64 | { 65 | Mapper_t *m = NULL; 66 | Data_Get_Struct (self, Mapper_t, m); 67 | if (!m) 68 | rb_raise (rb_eException, "No Mapper Object"); 69 | 70 | // TODO, what if some moron sends us a negative start value? 71 | unsigned _start = NUM2INT (start); 72 | unsigned _length = NUM2INT (length); 73 | if ((_start + _length) > m->GetFileSize()) 74 | rb_raise (rb_eException, "Mapper Range Error"); 75 | 76 | const char *chunk = m->GetChunk (_start); 77 | if (!chunk) 78 | rb_raise (rb_eException, "No Mapper Chunk"); 79 | return rb_str_new (chunk, _length); 80 | } 81 | 82 | /************ 83 | mapper_close 84 | ************/ 85 | 86 | static VALUE mapper_close (VALUE self) 87 | { 88 | Mapper_t *m = NULL; 89 | Data_Get_Struct (self, Mapper_t, m); 90 | if (!m) 91 | rb_raise (rb_eException, "No Mapper Object"); 92 | m->Close(); 93 | return Qnil; 94 | } 95 | 96 | /*********** 97 | mapper_size 98 | ***********/ 99 | 100 | static VALUE mapper_size (VALUE self) 101 | { 102 | Mapper_t *m = NULL; 103 | Data_Get_Struct (self, Mapper_t, m); 104 | if (!m) 105 | rb_raise (rb_eException, "No Mapper Object"); 106 | return INT2NUM (m->GetFileSize()); 107 | } 108 | 109 | 110 | /********************** 111 | Init_fastfilereaderext 112 | **********************/ 113 | 114 | extern "C" void Init_fastfilereaderext() 115 | { 116 | EmModule = rb_define_module ("EventMachine"); 117 | FastFileReader = rb_define_class_under (EmModule, "FastFileReader", rb_cObject); 118 | Mapper = rb_define_class_under (FastFileReader, "Mapper", rb_cObject); 119 | 120 | rb_define_module_function (Mapper, "new", (VALUE(*)(...))mapper_new, 1); 121 | rb_define_method (Mapper, "size", (VALUE(*)(...))mapper_size, 0); 122 | rb_define_method (Mapper, "close", (VALUE(*)(...))mapper_close, 0); 123 | rb_define_method (Mapper, "get_chunk", (VALUE(*)(...))mapper_get_chunk, 2); 124 | } 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/constants.rb: -------------------------------------------------------------------------------- 1 | module Swiftcore 2 | 3 | Deque = Array unless HasDeque or const_defined?(:Deque) 4 | 5 | module Swiftiply 6 | Version = '1.1.0' 7 | 8 | # Yeah, these constants look kind of tacky. Inside of tight loops, 9 | # though, using them makes a small but measurable difference, and those 10 | # small differences add up.... 11 | C_asterisk = '*'.freeze 12 | C_colon = ':'.freeze 13 | C_empty = ''.freeze 14 | C_header_close = 'HTTP/1.1 200 OK\r\nConnection: close\r\n'.freeze 15 | C_header_keepalive = 'HTTP/1.1 200 OK\r\n'.freeze 16 | C_localhost = '127.0.0.1'.freeze 17 | C_minus = '-'.freeze 18 | C_plus = '+'.freeze 19 | C_slash = '/'.freeze 20 | C_slashindex_html = '/index.html'.freeze 21 | C1_0 = '1.0'.freeze 22 | C1_1 = '1.1'.freeze 23 | C_304 = "HTTP/1.1 304 Not Modified\r\n".freeze 24 | C_date_header_range = 6..-5 25 | C80 = '80'.freeze 26 | Caos = 'application/octet-stream'.freeze 27 | Cat = 'at'.freeze 28 | Ccache_directory = 'cache_directory'.freeze 29 | Ccache_extensions = 'cache_extensions'.freeze 30 | Ccallsite = 'callsite'.freeze 31 | Cclassname = 'classname'.freeze 32 | Ccluster_address = 'cluster_address'.freeze 33 | Ccluster_port = 'cluster_port'.freeze 34 | Ccluster_server = 'cluster_server'.freeze 35 | CConnection_close = "Connection: close\r\n".freeze 36 | CConnection_KeepAlive = "Connection: Keep-Alive\r\n".freeze 37 | CBackendAddress = 'BackendAddress'.freeze 38 | CBackendPort = 'BackendPort'.freeze 39 | Cbackup = 'backup'.freeze 40 | Ccertfile = 'certfile'.freeze 41 | Cchunked_encoding_threshold = 'chunked_encoding_threshold'.freeze 42 | Cxforwardedfor = 'xforwardedfor'.freeze 43 | Cdaemonize = 'daemonize'.freeze 44 | Cdefault = 'default'.freeze 45 | CDELETE = 'DELETE'.freeze 46 | Cdescriptor_cache = 'descriptor_cache_threshold'.freeze 47 | Cdescriptors = 'descriptors'.freeze 48 | Cdirectory = 'directory'.freeze 49 | Cdocroot = 'docroot'.freeze 50 | Cdynamic_request_cache = 'dynamic_request_cache'.freeze 51 | Cenable_sendfile_404 = 'enable_sendfile_404'.freeze 52 | Cepoll = 'epoll'.freeze 53 | Cepoll_descriptors = 'epoll_descriptors'.freeze 54 | Cetag_cache = 'etag_cache'.freeze 55 | Cfile_cache = 'file_cache'.freeze 56 | CGET = 'GET'.freeze 57 | Cgroup = 'group'.freeze 58 | CHEAD = 'HEAD'.freeze 59 | Chealth_check_uri = 'health_check_uri'.freeze 60 | Chost = 'host'.freeze 61 | Cincoming = 'incoming'.freeze 62 | Cinfo = 'info'.freeze 63 | Ckeepalive = 'keepalive'.freeze 64 | Ckey = 'key'.freeze 65 | Ckeyfile = 'keyfile'.freeze 66 | Cmanager = 'manager'.freeze 67 | Cmap = 'map'.freeze 68 | Cmax_cache_size = 'max_cache_size'.freeze 69 | Cmsg_expired = 'browser connection expired'.freeze 70 | Coutgoing = 'outgoing'.freeze 71 | Cparams = 'params'.freeze 72 | Cport = 'port'.freeze 73 | CPOST = 'POST'.freeze 74 | Cproxy = 'proxy'.freeze 75 | CPUT = 'PUT'.freeze 76 | Credeployable = 'redeployable'.freeze 77 | Credeployment_sizelimit = 'redeployment_sizelimit'.freeze 78 | Crequire = 'require'.freeze 79 | Csendfileroot = 'sendfileroot'.freeze 80 | Cservers = 'servers'.freeze 81 | Cssl = 'ssl'.freeze 82 | Csize = 'size'.freeze 83 | Cstaticmask = 'staticmask'.freeze 84 | Cswiftclient = 'swiftclient'.freeze 85 | Cthreshold = 'threshold'.freeze 86 | Ctimeslice = 'timeslice'.freeze 87 | Ctimeout = 'timeout'.freeze 88 | Cupdates = 'updates'.freeze 89 | Curl = 'url'.freeze 90 | Cuser = 'user'.freeze 91 | Cwindow = 'window'.freeze 92 | 93 | C_fsep = File::SEPARATOR 94 | 95 | UnknownSocket = Socket::pack_sockaddr_in(0,'0.0.0.0') 96 | 97 | RunningConfig = {} 98 | 99 | class EMStartServerError < RuntimeError; end 100 | class SwiftiplyLoggerNotFound < RuntimeError; end 101 | 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /ext/fastfilereader/extconf.rb: -------------------------------------------------------------------------------- 1 | # $Id: extconf.rb 4526 2007-07-03 18:04:34Z francis $ 2 | # 3 | #---------------------------------------------------------------------------- 4 | # 5 | # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. 6 | # Gmail: garbagecat10 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of either: 1) the GNU General Public License 10 | # as published by the Free Software Foundation; either version 2 of the 11 | # License, or (at your option) any later version; or 2) Ruby's License. 12 | # 13 | # See the file COPYING for complete licensing information. 14 | # 15 | #--------------------------------------------------------------------------- 16 | # 17 | # extconf.rb for Fast File Reader 18 | # We have to munge LDSHARED because this code needs a C++ link. 19 | # 20 | 21 | require 'mkmf' 22 | 23 | flags = [] 24 | 25 | case RUBY_PLATFORM.split('-',2)[1] 26 | when 'mswin32', 'mingw32', 'bccwin32' 27 | unless have_header('windows.h') and 28 | have_header('winsock.h') and 29 | have_library('kernel32') and 30 | have_library('rpcrt4') and 31 | have_library('gdi32') 32 | exit 33 | end 34 | 35 | flags << "-D OS_WIN32" 36 | flags << '-D BUILD_FOR_RUBY' 37 | flags << "-EHs" 38 | flags << "-GR" 39 | 40 | dir_config('ssl') 41 | if have_library('ssleay32') and 42 | have_library('libeay32') and 43 | have_header('openssl/ssl.h') and 44 | have_header('openssl/err.h') 45 | flags << '-D WITH_SSL' 46 | else 47 | flags << '-D WITHOUT_SSL' 48 | end 49 | 50 | when /solaris/ 51 | unless have_library('pthread') and 52 | have_library('nsl') and 53 | have_library('socket') 54 | exit 55 | end 56 | 57 | flags << '-D OS_UNIX' 58 | flags << '-D OS_SOLARIS8' 59 | flags << '-D BUILD_FOR_RUBY' 60 | 61 | dir_config('ssl') 62 | if have_library('ssl') and 63 | have_library('crypto') and 64 | have_header('openssl/ssl.h') and 65 | have_header('openssl/err.h') 66 | flags << '-D WITH_SSL' 67 | else 68 | flags << '-D WITHOUT_SSL' 69 | end 70 | 71 | # on Unix we need a g++ link, not gcc. 72 | CONFIG['LDSHARED'] = "$(CXX) -shared" 73 | 74 | when /darwin/ 75 | flags << '-DOS_UNIX' 76 | flags << '-DBUILD_FOR_RUBY' 77 | 78 | dir_config('ssl') 79 | if have_library('ssl') and 80 | have_library('crypto') and 81 | have_library('C') and 82 | have_header('openssl/ssl.h') and 83 | have_header('openssl/err.h') 84 | flags << '-DWITH_SSL' 85 | else 86 | flags << '-DWITHOUT_SSL' 87 | end 88 | # on Unix we need a g++ link, not gcc. 89 | # Ff line contributed by Daniel Harple. 90 | CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ') 91 | 92 | when /linux/ 93 | unless have_library('pthread') 94 | exit 95 | end 96 | 97 | flags << '-DOS_UNIX' 98 | flags << '-DBUILD_FOR_RUBY' 99 | 100 | # Original epoll test is inadequate because 2.4 kernels have the header 101 | # but not the code. 102 | #flags << '-DHAVE_EPOLL' if have_header('sys/epoll.h') 103 | if have_header('sys/epoll.h') 104 | File.open("hasEpollTest.c", "w") {|f| 105 | f.puts "#include " 106 | f.puts "int main() { epoll_create(1024); return 0;}" 107 | } 108 | (e = system( "gcc hasEpollTest.c -o hasEpollTest " )) and (e = $?.to_i) 109 | `rm -f hasEpollTest.c hasEpollTest` 110 | flags << '-DHAVE_EPOLL' if e == 0 111 | end 112 | 113 | dir_config('ssl') 114 | if have_library('ssl') and 115 | have_library('crypto') and 116 | have_header('openssl/ssl.h') and 117 | have_header('openssl/err.h') 118 | flags << '-DWITH_SSL' 119 | else 120 | flags << '-DWITHOUT_SSL' 121 | end 122 | # on Unix we need a g++ link, not gcc. 123 | CONFIG['LDSHARED'] = "$(CXX) -shared" 124 | 125 | # Modify the mkmf constant LINK_SO so the generated shared object is stripped. 126 | # You might think modifying CONFIG['LINK_SO'] would be a better way to do this, 127 | # but it doesn't work because mkmf doesn't look at CONFIG['LINK_SO'] again after 128 | # it initializes. 129 | #linkso = Object.send :remove_const, "LINK_SO" 130 | #LINK_SO = linkso + "; strip $@" 131 | 132 | else 133 | unless have_library('pthread') 134 | exit 135 | end 136 | 137 | flags << '-DOS_UNIX' 138 | flags << '-DBUILD_FOR_RUBY' 139 | 140 | dir_config('ssl') 141 | if have_library('ssl') and 142 | have_library('crypto') and 143 | have_header('openssl/ssl.h') and 144 | have_header('openssl/err.h') 145 | flags << '-DWITH_SSL' 146 | else 147 | flags << '-DWITHOUT_SSL' 148 | end 149 | # on Unix we need a g++ link, not gcc. 150 | CONFIG['LDSHARED'] = "$(CXX) -shared" 151 | 152 | end 153 | 154 | if $CPPFLAGS 155 | $CPPFLAGS += ' ' + flags.join(' ') 156 | else 157 | $CFLAGS += ' ' + flags.join(' ') 158 | end 159 | 160 | 161 | create_makefile "fastfilereaderext" 162 | -------------------------------------------------------------------------------- /ext/map/extconf.rb: -------------------------------------------------------------------------------- 1 | # $Id: extconf.rb 4526 2007-07-03 18:04:34Z francis $ 2 | # 3 | #---------------------------------------------------------------------------- 4 | # 5 | # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. 6 | # Gmail: garbagecat10 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of either: 1) the GNU General Public License 10 | # as published by the Free Software Foundation; either version 2 of the 11 | # License, or (at your option) any later version; or 2) Ruby's License. 12 | # 13 | # See the file COPYING for complete licensing information. 14 | # 15 | #--------------------------------------------------------------------------- 16 | # 17 | # extconf.rb for Fast File Reader 18 | # We have to munge LDSHARED because this code needs a C++ link. 19 | # 20 | 21 | require 'mkmf' 22 | 23 | flags = [] 24 | 25 | case RUBY_PLATFORM.split('-',2)[1] 26 | when 'mswin32', 'mingw32', 'bccwin32' 27 | unless have_header('windows.h') and 28 | have_header('winsock.h') and 29 | have_library('kernel32') and 30 | have_library('rpcrt4') and 31 | have_library('gdi32') 32 | exit 33 | end 34 | 35 | flags << "-D OS_WIN32" 36 | flags << '-D BUILD_FOR_RUBY' 37 | flags << "-EHs" 38 | flags << "-GR" 39 | 40 | # dir_config('ssl') 41 | # if have_library('ssleay32') and 42 | # have_library('libeay32') and 43 | # have_header('openssl/ssl.h') and 44 | # have_header('openssl/err.h') 45 | # flags << '-D WITH_SSL' 46 | # else 47 | # flags << '-D WITHOUT_SSL' 48 | # end 49 | 50 | when /solaris/ 51 | # unless have_library('pthread') and 52 | # have_library('nsl') and 53 | # have_library('socket') 54 | # exit 55 | # end 56 | 57 | flags << '-D OS_UNIX' 58 | flags << '-D OS_SOLARIS8' 59 | flags << '-D BUILD_FOR_RUBY' 60 | 61 | # dir_config('ssl') 62 | # if have_library('ssl') and 63 | # have_library('crypto') and 64 | # have_header('openssl/ssl.h') and 65 | # have_header('openssl/err.h') 66 | # flags << '-D WITH_SSL' 67 | # else 68 | # flags << '-D WITHOUT_SSL' 69 | # end 70 | 71 | # on Unix we need a g++ link, not gcc. 72 | CONFIG['LDSHARED'] = "$(CXX) -shared" 73 | 74 | when /darwin/ 75 | flags << '-DOS_UNIX' 76 | flags << '-DBUILD_FOR_RUBY' 77 | 78 | # dir_config('ssl') 79 | # if have_library('ssl') and 80 | # have_library('crypto') and 81 | # have_library('C') and 82 | # have_header('openssl/ssl.h') and 83 | # have_header('openssl/err.h') 84 | # flags << '-DWITH_SSL' 85 | # else 86 | # flags << '-DWITHOUT_SSL' 87 | # end 88 | # on Unix we need a g++ link, not gcc. 89 | # Ff line contributed by Daniel Harple. 90 | CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ') 91 | 92 | when /linux/ 93 | unless have_library('pthread') 94 | exit 95 | end 96 | 97 | flags << '-DOS_UNIX' 98 | flags << '-DBUILD_FOR_RUBY' 99 | 100 | # Original epoll test is inadequate because 2.4 kernels have the header 101 | # but not the code. 102 | # #flags << '-DHAVE_EPOLL' if have_header('sys/epoll.h') 103 | # if have_header('sys/epoll.h') 104 | # File.open("hasEpollTest.c", "w") {|f| 105 | # f.puts "#include " 106 | # f.puts "int main() { epoll_create(1024); return 0;}" 107 | # } 108 | # (e = system( "gcc hasEpollTest.c -o hasEpollTest " )) and (e = $?.to_i) 109 | # `rm -f hasEpollTest.c hasEpollTest` 110 | # flags << '-DHAVE_EPOLL' if e == 0 111 | # end 112 | # 113 | # dir_config('ssl') 114 | # if have_library('ssl') and 115 | # have_library('crypto') and 116 | # have_header('openssl/ssl.h') and 117 | # have_header('openssl/err.h') 118 | # flags << '-DWITH_SSL' 119 | # else 120 | # flags << '-DWITHOUT_SSL' 121 | # end 122 | # on Unix we need a g++ link, not gcc. 123 | CONFIG['LDSHARED'] = "$(CXX) -shared" 124 | 125 | # Modify the mkmf constant LINK_SO so the generated shared object is stripped. 126 | # You might think modifying CONFIG['LINK_SO'] would be a better way to do this, 127 | # but it doesn't work because mkmf doesn't look at CONFIG['LINK_SO'] again after 128 | # it initializes. 129 | #linkso = Object.send :remove_const, "LINK_SO" 130 | #LINK_SO = linkso + "; strip $@" 131 | 132 | else 133 | # unless have_library('pthread') 134 | # exit 135 | # end 136 | 137 | flags << '-DOS_UNIX' 138 | flags << '-DBUILD_FOR_RUBY' 139 | 140 | # dir_config('ssl') 141 | # if have_library('ssl') and 142 | # have_library('crypto') and 143 | # have_header('openssl/ssl.h') and 144 | # have_header('openssl/err.h') 145 | # flags << '-DWITH_SSL' 146 | # else 147 | # flags << '-DWITHOUT_SSL' 148 | # end 149 | # on Unix we need a g++ link, not gcc. 150 | CONFIG['LDSHARED'] = "$(CXX) -shared" 151 | 152 | end 153 | 154 | if $CPPFLAGS 155 | $CPPFLAGS += ' ' + flags.join(' ') 156 | else 157 | $CFLAGS += ' ' + flags.join(' ') 158 | end 159 | 160 | 161 | create_makefile "map" 162 | -------------------------------------------------------------------------------- /ext/splaytree/extconf.rb: -------------------------------------------------------------------------------- 1 | # $Id: extconf.rb 4526 2007-07-03 18:04:34Z francis $ 2 | # 3 | #---------------------------------------------------------------------------- 4 | # 5 | # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. 6 | # Gmail: garbagecat10 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of either: 1) the GNU General Public License 10 | # as published by the Free Software Foundation; either version 2 of the 11 | # License, or (at your option) any later version; or 2) Ruby's License. 12 | # 13 | # See the file COPYING for complete licensing information. 14 | # 15 | #--------------------------------------------------------------------------- 16 | # 17 | # extconf.rb for Fast File Reader 18 | # We have to munge LDSHARED because this code needs a C++ link. 19 | # 20 | 21 | require 'mkmf' 22 | 23 | flags = [] 24 | 25 | case RUBY_PLATFORM.split('-',2)[1] 26 | when 'mswin32', 'mingw32', 'bccwin32' 27 | unless have_header('windows.h') and 28 | have_header('winsock.h') and 29 | have_library('kernel32') and 30 | have_library('rpcrt4') and 31 | have_library('gdi32') 32 | exit 33 | end 34 | 35 | flags << "-D OS_WIN32" 36 | flags << '-D BUILD_FOR_RUBY' 37 | flags << "-EHs" 38 | flags << "-GR" 39 | 40 | # dir_config('ssl') 41 | # if have_library('ssleay32') and 42 | # have_library('libeay32') and 43 | # have_header('openssl/ssl.h') and 44 | # have_header('openssl/err.h') 45 | # flags << '-D WITH_SSL' 46 | # else 47 | # flags << '-D WITHOUT_SSL' 48 | # end 49 | 50 | when /solaris/ 51 | # unless have_library('pthread') and 52 | # have_library('nsl') and 53 | # have_library('socket') 54 | # exit 55 | # end 56 | 57 | flags << '-D OS_UNIX' 58 | flags << '-D OS_SOLARIS8' 59 | flags << '-D BUILD_FOR_RUBY' 60 | 61 | # dir_config('ssl') 62 | # if have_library('ssl') and 63 | # have_library('crypto') and 64 | # have_header('openssl/ssl.h') and 65 | # have_header('openssl/err.h') 66 | # flags << '-D WITH_SSL' 67 | # else 68 | # flags << '-D WITHOUT_SSL' 69 | # end 70 | 71 | # on Unix we need a g++ link, not gcc. 72 | CONFIG['LDSHARED'] = "$(CXX) -shared" 73 | 74 | when /darwin/ 75 | flags << '-DOS_UNIX' 76 | flags << '-DBUILD_FOR_RUBY' 77 | 78 | # dir_config('ssl') 79 | # if have_library('ssl') and 80 | # have_library('crypto') and 81 | # have_library('C') and 82 | # have_header('openssl/ssl.h') and 83 | # have_header('openssl/err.h') 84 | # flags << '-DWITH_SSL' 85 | # else 86 | # flags << '-DWITHOUT_SSL' 87 | # end 88 | # on Unix we need a g++ link, not gcc. 89 | # Ff line contributed by Daniel Harple. 90 | CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ') 91 | 92 | when /linux/ 93 | unless have_library('pthread') 94 | exit 95 | end 96 | 97 | flags << '-DOS_UNIX' 98 | flags << '-DBUILD_FOR_RUBY' 99 | 100 | # Original epoll test is inadequate because 2.4 kernels have the header 101 | # but not the code. 102 | # #flags << '-DHAVE_EPOLL' if have_header('sys/epoll.h') 103 | # if have_header('sys/epoll.h') 104 | # File.open("hasEpollTest.c", "w") {|f| 105 | # f.puts "#include " 106 | # f.puts "int main() { epoll_create(1024); return 0;}" 107 | # } 108 | # (e = system( "gcc hasEpollTest.c -o hasEpollTest " )) and (e = $?.to_i) 109 | # `rm -f hasEpollTest.c hasEpollTest` 110 | # flags << '-DHAVE_EPOLL' if e == 0 111 | # end 112 | # 113 | # dir_config('ssl') 114 | # if have_library('ssl') and 115 | # have_library('crypto') and 116 | # have_header('openssl/ssl.h') and 117 | # have_header('openssl/err.h') 118 | # flags << '-DWITH_SSL' 119 | # else 120 | # flags << '-DWITHOUT_SSL' 121 | # end 122 | # on Unix we need a g++ link, not gcc. 123 | CONFIG['LDSHARED'] = "$(CXX) -shared" 124 | 125 | # Modify the mkmf constant LINK_SO so the generated shared object is stripped. 126 | # You might think modifying CONFIG['LINK_SO'] would be a better way to do this, 127 | # but it doesn't work because mkmf doesn't look at CONFIG['LINK_SO'] again after 128 | # it initializes. 129 | #linkso = Object.send :remove_const, "LINK_SO" 130 | #LINK_SO = linkso + "; strip $@" 131 | 132 | else 133 | # unless have_library('pthread') 134 | # exit 135 | # end 136 | 137 | flags << '-DOS_UNIX' 138 | flags << '-DBUILD_FOR_RUBY' 139 | 140 | # dir_config('ssl') 141 | # if have_library('ssl') and 142 | # have_library('crypto') and 143 | # have_header('openssl/ssl.h') and 144 | # have_header('openssl/err.h') 145 | # flags << '-DWITH_SSL' 146 | # else 147 | # flags << '-DWITHOUT_SSL' 148 | # end 149 | # on Unix we need a g++ link, not gcc. 150 | CONFIG['LDSHARED'] = "$(CXX) -shared" 151 | 152 | end 153 | 154 | if $CPPFLAGS 155 | $CPPFLAGS += ' ' + flags.join(' ') 156 | else 157 | $CFLAGS += ' ' + flags.join(' ') 158 | end 159 | 160 | 161 | create_makefile("swiftcore/splaytreemap","swiftcore") 162 | -------------------------------------------------------------------------------- /ext/deque/extconf.rb: -------------------------------------------------------------------------------- 1 | # $Id: extconf.rb 4526 2007-07-03 18:04:34Z francis $ 2 | # 3 | #---------------------------------------------------------------------------- 4 | # 5 | # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. 6 | # Gmail: garbagecat10 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of either: 1) the GNU General Public License 10 | # as published by the Free Software Foundation; either version 2 of the 11 | # License, or (at your option) any later version; or 2) Ruby's License. 12 | # 13 | # See the file COPYING for complete licensing information. 14 | # 15 | #--------------------------------------------------------------------------- 16 | # 17 | # extconf.rb for Fast File Reader 18 | # We have to munge LDSHARED because this code needs a C++ link. 19 | # 20 | 21 | require 'mkmf' 22 | 23 | flags = [] 24 | 25 | case RUBY_PLATFORM.split('-',2)[1] 26 | when 'mswin32', 'mingw32', 'bccwin32' 27 | unless have_header('windows.h') and 28 | have_header('winsock.h') and 29 | have_library('kernel32') and 30 | have_library('rpcrt4') and 31 | have_library('gdi32') 32 | exit 33 | end 34 | 35 | flags << "-D OS_WIN32" 36 | flags << '-D BUILD_FOR_RUBY' 37 | flags << "-EHs" 38 | flags << "-GR" 39 | 40 | # dir_config('ssl') 41 | # if have_library('ssleay32') and 42 | # have_library('libeay32') and 43 | # have_header('openssl/ssl.h') and 44 | # have_header('openssl/err.h') 45 | # flags << '-D WITH_SSL' 46 | # else 47 | # flags << '-D WITHOUT_SSL' 48 | # end 49 | 50 | when /solaris/ 51 | # unless have_library('pthread') and 52 | # have_library('nsl') and 53 | # have_library('socket') 54 | # exit 55 | # end 56 | 57 | flags << '-D OS_UNIX' 58 | flags << '-D OS_SOLARIS8' 59 | flags << '-D BUILD_FOR_RUBY' 60 | 61 | # dir_config('ssl') 62 | # if have_library('ssl') and 63 | # have_library('crypto') and 64 | # have_header('openssl/ssl.h') and 65 | # have_header('openssl/err.h') 66 | # flags << '-D WITH_SSL' 67 | # else 68 | # flags << '-D WITHOUT_SSL' 69 | # end 70 | 71 | # on Unix we need a g++ link, not gcc. 72 | CONFIG['LDSHARED'] = "$(CXX) -shared" 73 | 74 | when /darwin/ 75 | flags << '-DOS_UNIX' 76 | flags << '-DBUILD_FOR_RUBY' 77 | 78 | # dir_config('ssl') 79 | # if have_library('ssl') and 80 | # have_library('crypto') and 81 | # have_library('C') and 82 | # have_header('openssl/ssl.h') and 83 | # have_header('openssl/err.h') 84 | # flags << '-DWITH_SSL' 85 | # else 86 | # flags << '-DWITHOUT_SSL' 87 | # end 88 | # on Unix we need a g++ link, not gcc. 89 | # Ff line contributed by Daniel Harple. 90 | CONFIG['LDSHARED'] = "$(CXX) " + CONFIG['LDSHARED'].split[1..-1].join(' ') 91 | 92 | when /linux/ 93 | unless have_library('pthread') 94 | exit 95 | end 96 | 97 | flags << '-DOS_UNIX' 98 | flags << '-DBUILD_FOR_RUBY' 99 | 100 | # Original epoll test is inadequate because 2.4 kernels have the header 101 | # but not the code. 102 | # #flags << '-DHAVE_EPOLL' if have_header('sys/epoll.h') 103 | # if have_header('sys/epoll.h') 104 | # File.open("hasEpollTest.c", "w") {|f| 105 | # f.puts "#include " 106 | # f.puts "int main() { epoll_create(1024); return 0;}" 107 | # } 108 | # (e = system( "gcc hasEpollTest.c -o hasEpollTest " )) and (e = $?.to_i) 109 | # `rm -f hasEpollTest.c hasEpollTest` 110 | # flags << '-DHAVE_EPOLL' if e == 0 111 | # end 112 | # 113 | # dir_config('ssl') 114 | # if have_library('ssl') and 115 | # have_library('crypto') and 116 | # have_header('openssl/ssl.h') and 117 | # have_header('openssl/err.h') 118 | # flags << '-DWITH_SSL' 119 | # else 120 | # flags << '-DWITHOUT_SSL' 121 | # end 122 | # on Unix we need a g++ link, not gcc. 123 | CONFIG['LDSHARED'] = "$(CXX) -shared" 124 | 125 | # Modify the mkmf constant LINK_SO so the generated shared object is stripped. 126 | # You might think modifying CONFIG['LINK_SO'] would be a better way to do this, 127 | # but it doesn't work because mkmf doesn't look at CONFIG['LINK_SO'] again after 128 | # it initializes. 129 | #linkso = Object.send :remove_const, "LINK_SO" 130 | #LINK_SO = linkso + "; strip $@" 131 | 132 | else 133 | # unless have_library('pthread') 134 | # exit 135 | # end 136 | 137 | flags << '-DOS_UNIX' 138 | flags << '-DBUILD_FOR_RUBY' 139 | 140 | # dir_config('ssl') 141 | # if have_library('ssl') and 142 | # have_library('crypto') and 143 | # have_header('openssl/ssl.h') and 144 | # have_header('openssl/err.h') 145 | # flags << '-DWITH_SSL' 146 | # else 147 | # flags << '-DWITHOUT_SSL' 148 | # end 149 | # on Unix we need a g++ link, not gcc. 150 | CONFIG['LDSHARED'] = "$(CXX) -shared" 151 | 152 | end 153 | 154 | if $CPPFLAGS 155 | $CPPFLAGS += ' ' + flags.join(' ') 156 | else 157 | $CFLAGS += ' ' + flags.join(' ') 158 | end 159 | 160 | 161 | #create_makefile("deque","swiftcore") 162 | create_makefile("swiftcore/deque","swiftcore") 163 | -------------------------------------------------------------------------------- /ext/fastfilereader/mapper.cpp: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | $Id: mapper.cpp 4527 2007-07-04 10:21:34Z francis $ 4 | 5 | File: mapper.cpp 6 | Date: 02Jul07 7 | 8 | Copyright (C) 2007 by Francis Cianfrocca. All Rights Reserved. 9 | Gmail: garbagecat10 10 | 11 | This program is free software; you can redistribute it and/or modify 12 | it under the terms of either: 1) the GNU General Public License 13 | as published by the Free Software Foundation; either version 2 of the 14 | License, or (at your option) any later version; or 2) Ruby's License. 15 | 16 | See the file COPYING for complete licensing information. 17 | 18 | *****************************************************************************/ 19 | 20 | 21 | ////////////////////////////////////////////////////////////////////// 22 | // UNIX implementation 23 | ////////////////////////////////////////////////////////////////////// 24 | 25 | 26 | #ifdef OS_UNIX 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include "unistd.h" 36 | #include 37 | #include 38 | #include 39 | using namespace std; 40 | 41 | #include "mapper.h" 42 | 43 | /****************** 44 | Mapper_t::Mapper_t 45 | ******************/ 46 | 47 | Mapper_t::Mapper_t (const string &filename) 48 | { 49 | /* We ASSUME we can open the file. 50 | * (More precisely, we assume someone else checked before we got here.) 51 | */ 52 | 53 | Fd = open (filename.c_str(), O_RDONLY); 54 | if (Fd < 0) 55 | throw runtime_error (strerror (errno)); 56 | 57 | struct stat st; 58 | if (fstat (Fd, &st)) 59 | throw runtime_error (strerror (errno)); 60 | FileSize = st.st_size; 61 | 62 | MapPoint = (const char*) mmap (0, FileSize, PROT_READ, MAP_SHARED, Fd, 0); 63 | if (MapPoint == MAP_FAILED) 64 | throw runtime_error (strerror (errno)); 65 | } 66 | 67 | 68 | /******************* 69 | Mapper_t::~Mapper_t 70 | *******************/ 71 | 72 | Mapper_t::~Mapper_t() 73 | { 74 | Close(); 75 | } 76 | 77 | 78 | /*************** 79 | Mapper_t::Close 80 | ***************/ 81 | 82 | void Mapper_t::Close() 83 | { 84 | // Can be called multiple times. 85 | // Calls to GetChunk are invalid after a call to Close. 86 | if (MapPoint) { 87 | munmap ((void*)MapPoint, FileSize); 88 | MapPoint = NULL; 89 | } 90 | if (Fd >= 0) { 91 | close (Fd); 92 | Fd = -1; 93 | } 94 | } 95 | 96 | /****************** 97 | Mapper_t::GetChunk 98 | ******************/ 99 | 100 | const char *Mapper_t::GetChunk (unsigned start) 101 | { 102 | return MapPoint + start; 103 | } 104 | 105 | 106 | 107 | #endif // OS_UNIX 108 | 109 | 110 | ////////////////////////////////////////////////////////////////////// 111 | // WINDOWS implementation 112 | ////////////////////////////////////////////////////////////////////// 113 | 114 | #ifdef OS_WIN32 115 | 116 | #include 117 | 118 | #include 119 | #include 120 | #include 121 | using namespace std; 122 | 123 | #include "mapper.h" 124 | 125 | /****************** 126 | Mapper_t::Mapper_t 127 | ******************/ 128 | 129 | Mapper_t::Mapper_t (const string &filename) 130 | { 131 | /* We ASSUME we can open the file. 132 | * (More precisely, we assume someone else checked before we got here.) 133 | */ 134 | 135 | hFile = INVALID_HANDLE_VALUE; 136 | hMapping = NULL; 137 | MapPoint = NULL; 138 | FileSize = 0; 139 | 140 | hFile = CreateFile (filename.c_str(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 141 | 142 | if (hFile == INVALID_HANDLE_VALUE) 143 | throw runtime_error ("File not found"); 144 | 145 | BY_HANDLE_FILE_INFORMATION i; 146 | if (GetFileInformationByHandle (hFile, &i)) 147 | FileSize = i.nFileSizeLow; 148 | 149 | hMapping = CreateFileMapping (hFile, NULL, PAGE_READWRITE, 0, 0, NULL); 150 | if (!hMapping) 151 | throw runtime_error ("File not mapped"); 152 | 153 | MapPoint = (const char*) MapViewOfFile (hMapping, FILE_MAP_WRITE, 0, 0, 0); 154 | if (!MapPoint) 155 | throw runtime_error ("Mappoint not read"); 156 | } 157 | 158 | 159 | /******************* 160 | Mapper_t::~Mapper_t 161 | *******************/ 162 | 163 | Mapper_t::~Mapper_t() 164 | { 165 | Close(); 166 | } 167 | 168 | /*************** 169 | Mapper_t::Close 170 | ***************/ 171 | 172 | void Mapper_t::Close() 173 | { 174 | // Can be called multiple times. 175 | // Calls to GetChunk are invalid after a call to Close. 176 | if (MapPoint) { 177 | UnmapViewOfFile (MapPoint); 178 | MapPoint = NULL; 179 | } 180 | if (hMapping != NULL) { 181 | CloseHandle (hMapping); 182 | hMapping = NULL; 183 | } 184 | if (hFile != INVALID_HANDLE_VALUE) { 185 | CloseHandle (hFile); 186 | hMapping = INVALID_HANDLE_VALUE; 187 | } 188 | } 189 | 190 | 191 | /****************** 192 | Mapper_t::GetChunk 193 | ******************/ 194 | 195 | const char *Mapper_t::GetChunk (unsigned start) 196 | { 197 | return MapPoint + start; 198 | } 199 | 200 | 201 | 202 | #endif // OS_WINDOWS 203 | -------------------------------------------------------------------------------- /test/TC_ProxyBag.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'external/test_support' 3 | SwiftcoreTestSupport.set_src_dir 4 | require 'rbconfig' 5 | require 'net/http' 6 | require 'swiftcore/Swiftiply' 7 | require 'yaml' 8 | 9 | class TC_ProxyBag < Minitest::Test 10 | @@testdir = SwiftcoreTestSupport.test_dir(__FILE__) 11 | Ruby = File.join(::RbConfig::CONFIG['bindir'],::RbConfig::CONFIG['ruby_install_name']) << ::RbConfig::CONFIG['EXEEXT'] 12 | 13 | DeleteQueue = [] 14 | KillQueue = [] 15 | 16 | ConfBase = YAML.load(< e 10 | unless load_attempted 11 | load_attempted = true 12 | require 'rubygems' 13 | retry 14 | end 15 | raise e 16 | end 17 | 18 | module Swiftcore 19 | class SwiftiplyExec 20 | Ccluster_address = 'cluster_address'.freeze 21 | Ccluster_port = 'cluster_port'.freeze 22 | Cconfig_file = 'config_file'.freeze 23 | Cbackend_address = 'backend_address'.freeze 24 | Cbackend_port = 'backend_port'.freeze 25 | Cmap = 'map'.freeze 26 | Cincoming = 'incoming'.freeze 27 | Coutgoing = 'outgoing'.freeze 28 | Ckeepalive = 'keepalive'.freeze 29 | Cdaemonize = 'daemonize'.freeze 30 | Curl = 'url'.freeze 31 | Chost = 'host'.freeze 32 | Cport = 'port'.freeze 33 | Ctimeout = 'timeout'.freeze 34 | 35 | ##### 36 | # 37 | # --cluster-address 38 | # --cluster-port 39 | # --config-file -c (default swiftiply.cnf) 40 | # --daemonize -d 41 | # 42 | # Config file format (YAML): 43 | # 44 | # cluster_address: 45 | # cluster_port: 46 | # daemonize: 47 | # map: 48 | # - incoming: 49 | # - 127.0.0.1:8080 50 | # - foo.bar.com:8090 51 | # url: 52 | # keepalive: 53 | # outgoing: 54 | # 55 | ##### 56 | def self.parse_options 57 | config = @cliconfig || {} 58 | @print = false 59 | 60 | OptionParser.new do |opts| 61 | opts.banner = 'Usage: swiftiply.rb [options]' 62 | opts.separator '' 63 | opts.on('-c','--config CONFFILE',"The configuration file to read.") do |conf| 64 | config[Cconfig_file] = conf 65 | end 66 | opts.on('--cluster-address [ADDRESS]',String,'The hostname/IP address that swiftiply will listen for connections on.') do |address| 67 | config[Ccluster_address] = address 68 | end 69 | opts.on('--cluster-port [PORT]',Integer,'The port that swiftiply will listen for connections on.') do |port| 70 | config[Ccluster_port] = port 71 | end 72 | opts.on('--backend-address [ADDRESS]',String,'The hostname/IP address that swiftiply will listen for backend connections on.') do |address| 73 | config[Cbackend_address] = address 74 | end 75 | opts.on('--backend-port [PORT]',Integer,'The port that swiftiply will listen for backend connections on.') do |port| 76 | config[Cbackend_port] = port 77 | end 78 | opts.on('-d','--daemonize [YN]',[:y,:yes,:n,:no],'Whether swiftiply should put itself into the background.') do |yn| 79 | config[Cdaemonize] = yn.to_s =~ /^y/i 80 | end 81 | opts.on('-t','--timeout [SECONDS]',Integer,'The server unavailable timeout. Defaults to 3 seconds.') do |timeout| 82 | config[Ctimeout] = timeout 83 | end 84 | opts.on('-p','--print-config','Print the full configuration.') do 85 | @print = true 86 | end 87 | opts.on('-v','--version','Show the version number, then exit.') do 88 | puts "Swiftiply v. #{Swiftcore::Swiftiply::VERSION}" 89 | exit 0 90 | end 91 | opts.on('-f','--pid PIDFILE',"Path of the pid file to write.") do |pidfile| 92 | config['pidfile'] = pidfile 93 | end 94 | 95 | end.parse! 96 | @cliconfig ||= config 97 | 98 | fileconfig = {} 99 | fileconfig = YAML.load(File.open(config['config_file'])) if config['config_file'] 100 | config = fileconfig.merge(@cliconfig) 101 | postprocess_config_load(config) 102 | (puts("Configuration failed validation; exiting.") && exit(1)) unless verify_config(config) 103 | 104 | if @print 105 | require 'pp' 106 | pp config 107 | exit 0 108 | end 109 | 110 | config 111 | end 112 | 113 | def self.daemonize(pidfile = nil) 114 | if (child_pid = fork) 115 | if pidfile 116 | begin 117 | File.open(pidfile,"w+") {|fh| fh.puts "#{child_pid}"} 118 | rescue Exception => e 119 | puts "Failed to write PID file #{pidfile}\n#{e}\n\nPID #{child_pid}" 120 | end 121 | else 122 | puts "PID #{child_pid}" 123 | end 124 | 125 | exit! 0 126 | end 127 | 128 | Process.setsid 129 | 130 | rescue Exception 131 | puts "Platform (#{RUBY_PLATFORM}) does not appear to support fork/setsid; skipping" 132 | end 133 | 134 | def self.postprocess_config_load(config) 135 | config[Cmap] = [] unless Array === config[Cmap] 136 | config[Ctimeout] ||= 3 137 | config[Cmap].each do |m| 138 | m[Ckeepalive] = true unless m.has_key?(Ckeepalive) 139 | m[Ckeepalive] = !!m[Ckeepalive] 140 | m[Coutgoing] = [m[Coutgoing]] unless Array === m[Coutgoing] 141 | m[Cincoming] = [m[Cincoming]] unless Array === m[Cincoming] 142 | end 143 | end 144 | 145 | def self.verify_config(config) 146 | return nil if config[Ccluster_address].nil? 147 | return nil if config[Ccluster_port].nil? 148 | 149 | if config[Cbackend_address] and config[Cbackend_port] 150 | config[Cmap] << {Cincoming => nil, Curl => nil, Coutgoing => "#{config[Cbackend_address]}:#{config[Cbackend_port]}", Ckeepalive => true} 151 | end 152 | true 153 | end 154 | 155 | def self.run 156 | config = parse_options 157 | daemonize(config['pidfile']) if config[Cdaemonize] 158 | Swiftcore::Swiftiply.run(config) 159 | end 160 | 161 | def self.on_windows? 162 | return @on_windows unless @on_windows.nil? 163 | @on_windows = !![/mswin/i, /cygwin/i, /mingw/i, /bccwin/i, /wince/i].find {|p| RUBY_PLATFORM =~ p} 164 | end 165 | end 166 | end 167 | 168 | Swiftcore::SwiftiplyExec.run 169 | exit 0 170 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/backend_protocol.rb: -------------------------------------------------------------------------------- 1 | module Swiftcore 2 | module Swiftiply 3 | 4 | # The BackendProtocol is the EventMachine::Connection subclass that 5 | # handles the communications between Swiftiply and the backend process 6 | # it is proxying to. 7 | 8 | class BackendProtocol < EventMachine::Connection 9 | attr_accessor :associate, :id 10 | 11 | C0rnrn = "0\r\n\r\n".freeze 12 | Crnrn = "\r\n\r\n".freeze 13 | 14 | def initialize *args 15 | @name = self.class.bname 16 | @permit_xsendfile = self.class.xsendfile 17 | @enable_sendfile_404 = self.class.enable_sendfile_404 18 | super 19 | end 20 | 21 | def name 22 | @name 23 | end 24 | 25 | # Call setup() and add the backend to the ProxyBag queue. 26 | 27 | def post_init 28 | setup 29 | @initialized = nil 30 | ProxyBag.add_server self 31 | end 32 | 33 | # Setup the initial variables for receiving headers and content. 34 | 35 | def setup 36 | @headers = '' 37 | @headers_completed = @dont_send_data = false 38 | #@content_length = nil 39 | @content_sent = 0 40 | @filter = self.class.filter 41 | end 42 | 43 | # Receive data from the backend process. Headers are parsed from 44 | # the rest of the content. If a Content-Length header is present, 45 | # that is used to determine how much data to expect. Otherwise, 46 | # if 'Transfer-encoding: chunked' is present, assume chunked 47 | # encoding. Otherwise be paranoid; something isn't the way we like 48 | # it to be. 49 | 50 | def receive_data data 51 | unless @initialized 52 | # preamble = data.slice!(0..24) 53 | preamble = data[0..24] 54 | data = data[25..-1] || C_empty 55 | keylen = preamble[23..24].to_i(16) 56 | keylen = 0 if keylen < 0 57 | key = keylen > 0 ? data.slice!(0..(keylen - 1)) : C_empty 58 | #if preamble[0..10] == Cswiftclient and key == ProxyBag.get_key(@name) 59 | if preamble.index(Cswiftclient) == 0 and key == ProxyBag.get_key(@name) 60 | @id = preamble[11..22] 61 | ProxyBag.add_id(self,@id) 62 | @initialized = true 63 | else 64 | # The worker that connected did not present the proper authentication, 65 | # so something is fishy; time to cut bait. 66 | close_connection 67 | return 68 | end 69 | end 70 | 71 | unless @headers_completed 72 | if data.include?(Crnrn) 73 | @headers_completed = true 74 | h,data = data.split(/\r\n\r\n/,2) 75 | #@headers << h << Crnrn 76 | if @headers.length > 0 77 | @headers << h 78 | else 79 | @headers = h 80 | end 81 | 82 | if @headers =~ /Content-[Ll]ength: *([^\r]+)/ 83 | @content_length = $1.to_i 84 | elsif @headers =~ /Transfer-encoding:\s*chunked/ 85 | @content_length = nil 86 | else 87 | @content_length = 0 88 | end 89 | 90 | if @permit_xsendfile && @headers =~ /X-[Ss]endfile: *([^\r]+)/ 91 | @associate.uri = $1 92 | if ProxyBag.serve_static_file(@associate,ProxyBag.get_sendfileroot(@associate.name)) 93 | @dont_send_data = true 94 | else 95 | if @enable_sendfile_404 96 | msg = "#{@associate.uri} could not be found." 97 | @associate.send_data "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\nContent-Type: text/html\r\nContent-Length: #{msg.length}\r\n\r\n#{msg}" 98 | @associate.close_connection_after_writing 99 | @dont_send_data = true 100 | else 101 | @associate.send_data @headers + Crnrn 102 | end 103 | end 104 | else 105 | @associate.send_data @headers + Crnrn 106 | end 107 | 108 | # If keepalive is turned on, the assumption is that it will stay 109 | # on, unless the headers being returned indicate that the connection 110 | # should be closed. 111 | # So, check for a 'Connection: Closed' header. 112 | if keepalive = @associate.keepalive 113 | keepalive = false if @headers =~ /Connection: [Cc]lose/ 114 | if @associate_http_version == C1_0 115 | keepalive = false unless @headers == /Connection: Keep-Alive/i 116 | end 117 | end 118 | else 119 | @headers << data 120 | end 121 | end 122 | 123 | if @headers_completed 124 | @associate.send_data data unless @dont_send_data 125 | @content_sent += data.length 126 | if @content_length and @content_sent >= @content_length or data[-6..-1] == C0rnrn 127 | # If @dont_send_data is set, then the connection is going to be closed elsewhere. 128 | unless @dont_send_data 129 | # Check to see if keepalive is enabled. 130 | if keepalive 131 | @associate.reset_state 132 | ProxyBag.remove_client(self) unless @associate 133 | else 134 | @associate.close_connection_after_writing 135 | end 136 | end 137 | @associate = @headers_completed = @dont_send_data = nil 138 | @headers = '' 139 | #@headers_completed = false 140 | #@content_length = nil 141 | @content_sent = 0 142 | #setup 143 | ProxyBag.add_server self 144 | end 145 | end 146 | # TODO: Log these errors! 147 | rescue Exception => e 148 | puts "Kaboom: #{e} -- #{e.backtrace.inspect}" 149 | @associate.close_connection_after_writing if @associate 150 | @associate = nil 151 | setup 152 | ProxyBag.add_server self 153 | end 154 | 155 | # This is called when the backend disconnects from the proxy. 156 | # If the backend is currently associated with a web browser client, 157 | # that connection will be closed. Otherwise, the backend will be 158 | # removed from the ProxyBag's backend queue. 159 | 160 | def unbind 161 | if @associate 162 | if !@associate.redeployable or @content_length 163 | @associate.close_connection_after_writing 164 | else 165 | @associate.associate = nil 166 | @associate.setup_for_redeployment 167 | ProxyBag.rebind_frontend_client(@associate) 168 | end 169 | else 170 | ProxyBag.remove_server(self) 171 | end 172 | ProxyBag.remove_id(self) 173 | end 174 | 175 | def self.bname=(val) 176 | @bname = val 177 | end 178 | 179 | def self.bname 180 | @bname 181 | end 182 | 183 | def self.xsendfile=(val) 184 | @xsendfile = val 185 | end 186 | 187 | def self.xsendfile 188 | @xsendfile 189 | end 190 | 191 | def self.enable_sendfile_404=(val) 192 | @enable_sendfile_404 = val 193 | end 194 | 195 | def self.enable_sendfile_404 196 | @enable_sendfile_404 197 | end 198 | 199 | def self.filter=(val) 200 | @filter = val 201 | end 202 | 203 | def self.filter 204 | @filter 205 | end 206 | 207 | def filter 208 | @filter 209 | end 210 | end 211 | 212 | end 213 | end 214 | -------------------------------------------------------------------------------- /src/swiftcore/evented_mongrel.rb: -------------------------------------------------------------------------------- 1 | # This module rewrites pieces of the very good Mongrel web server in 2 | # order to change it from a threaded application to an event based 3 | # application running inside an EventMachine event loop. It should 4 | # be compatible with the existing Mongrel handlers for Rails, 5 | # Camping, Nitro, etc.... 6 | 7 | begin 8 | load_attempted ||= false 9 | require 'eventmachine' 10 | rescue LoadError 11 | unless load_attempted 12 | load_attempted = true 13 | require 'rubygems' 14 | retry 15 | end 16 | end 17 | 18 | require 'mongrel' 19 | 20 | module Mongrel 21 | class MongrelProtocol < EventMachine::Connection 22 | 23 | Cblank = ''.freeze 24 | C400Header = "HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\nServer: Swiftiplied Mongrel 0.6.5\r\nConnection: close\r\n\r\n" 25 | 26 | def post_init 27 | @parser = HttpParser.new 28 | @params = HttpParams.new 29 | @nparsed = 0 30 | @request = nil 31 | @request_len = nil 32 | @linebuffer = '' 33 | end 34 | 35 | def receive_data data 36 | @linebuffer << data 37 | @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished? 38 | if @parser.finished? 39 | if @request_len.nil? 40 | @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i 41 | script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH] || Cblank) 42 | if handlers 43 | @params[::Mongrel::Const::PATH_INFO] = path_info 44 | @params[::Mongrel::Const::SCRIPT_NAME] = script_name 45 | # The previous behavior of this line set REMOTE_ADDR equal to HTTP_X_FORWARDED_FOR 46 | # if it was defined. This behavior seems inconsistent with the intention of 47 | # http://www.ietf.org/rfc/rfc3875 so it has been changed. REMOTE_ADDR now always 48 | # contains the address of the immediate source of the connection. Check 49 | # @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] if you need that information. 50 | @params[::Mongrel::Const::REMOTE_ADDR] = ::Socket.unpack_sockaddr_in(get_peername)[1] rescue nil 51 | @notifiers = handlers.select { |h| h.request_notify } 52 | end 53 | if @request_len > ::Mongrel::Const::MAX_BODY 54 | new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE) 55 | new_buffer.binmode 56 | new_buffer << @linebuffer[@nparsed..-1] 57 | @linebuffer = new_buffer 58 | else 59 | @linebuffer = StringIO.new(@linebuffer[@nparsed..-1]) 60 | @linebuffer.pos = @linebuffer.length 61 | end 62 | end 63 | if @linebuffer.length >= @request_len 64 | @linebuffer.rewind 65 | ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self) 66 | @linebuffer.delete if Tempfile === @linebuffer 67 | end 68 | elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER 69 | close_connection 70 | raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.") 71 | end 72 | rescue ::Mongrel::HttpParserError 73 | if $mongrel_debug_client 74 | STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!" 75 | STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n" 76 | end 77 | send_data C400Header 78 | close_connection_after_writing 79 | rescue Exception => e 80 | raise e 81 | send_data C400Header 82 | close_connection_after_writing 83 | end 84 | 85 | def write data 86 | send_data data 87 | end 88 | 89 | def closed? 90 | false 91 | end 92 | 93 | end 94 | 95 | class HttpServer 96 | def initialize(host, port, num_processors=950, x=0, y=nil) # Deal with Mongrel 1.0.1 or earlier, as well as later. 97 | @socket = nil 98 | @classifier = URIClassifier.new 99 | @host = host 100 | @port = port 101 | @workers = ThreadGroup.new 102 | if y 103 | @throttle = x 104 | @timeout = y || 60 105 | else 106 | @timeout = x 107 | end 108 | @num_processors = num_processors #num_processors is pointless for evented.... 109 | @death_time = 60 110 | self.class.const_set(:Instance,self) 111 | end 112 | 113 | def run 114 | trap('INT') { raise StopServer } 115 | trap('TERM') { raise StopServer } 116 | #@acceptor = Thread.new do 117 | @acceptor = Thread.current 118 | # SHOULD NOT DO THIS AUTOMATICALLY. 119 | # There either needs to be a way to configure this, or to detect 120 | # when it is safe or when kqueue needs to run. 121 | EventMachine.epoll 122 | EventMachine.set_descriptor_table_size(4096) 123 | EventMachine.run do 124 | EM.set_timer_quantum(5) 125 | begin 126 | EventMachine.start_server(@host,@port.to_i,MongrelProtocol) 127 | rescue StopServer 128 | EventMachine.stop_event_loop 129 | end 130 | end 131 | #end 132 | end 133 | 134 | def process_http_request(params,linebuffer,client) 135 | if not params[Const::REQUEST_PATH] 136 | uri = URI.parse(params[Const::REQUEST_URI]) 137 | params[Const::REQUEST_PATH] = uri.path 138 | end 139 | 140 | raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] 141 | 142 | script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) 143 | 144 | if handlers 145 | notifiers = handlers.select { |h| h.request_notify } 146 | request = HttpRequest.new(params, linebuffer, notifiers) 147 | 148 | # TODO: Add Keep-Alive support 149 | 150 | # request is good so far, continue processing the response 151 | response = HttpResponse.new(client) 152 | 153 | # Process each handler in registered order until we run out or one finalizes the response. 154 | dispatch_to_handlers(handlers,request,response) 155 | 156 | # And finally, if nobody closed the response off, we finalize it. 157 | unless response.done 158 | response.finished 159 | else 160 | response.close_connection_after_writing 161 | end 162 | else 163 | # Didn't find it, return a stock 404 response. 164 | client.send_data(Const::ERROR_404_RESPONSE) 165 | client.close_connection_after_writing 166 | end 167 | end 168 | 169 | def dispatch_to_handlers(handlers,request,response) 170 | handlers.each do |handler| 171 | handler.process(request, response) 172 | break if response.done 173 | end 174 | end 175 | end 176 | 177 | class HttpRequest 178 | def initialize(params, linebuffer, dispatchers) 179 | @params = params 180 | @dispatchers = dispatchers 181 | @body = linebuffer 182 | end 183 | end 184 | 185 | class HttpResponse 186 | def send_file(path, small_file = false) 187 | File.open(path, "rb") do |f| 188 | while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0 189 | begin 190 | write(chunk) 191 | rescue Object => exc 192 | break 193 | end 194 | end 195 | end 196 | @body_sent = true 197 | end 198 | 199 | def write(data) 200 | @socket.send_data data 201 | end 202 | 203 | def close_connection_after_writing 204 | @socket.close_connection_after_writing 205 | end 206 | 207 | def socket_error(details) 208 | @socket.close_connection 209 | done = true 210 | raise details 211 | end 212 | 213 | def finished 214 | send_status 215 | send_header 216 | send_body 217 | @socket.close_connection_after_writing 218 | end 219 | end 220 | 221 | class Configurator 222 | # This version fixes a bug in the regular Mongrel version by adding 223 | # initialization of groups. 224 | def change_privilege(user, group) 225 | if user and group 226 | log "Initializing groups for {#user}:{#group}." 227 | Process.initgroups(user,Etc.getgrnam(group).gid) 228 | end 229 | 230 | if group 231 | log "Changing group to #{group}." 232 | Process::GID.change_privilege(Etc.getgrnam(group).gid) 233 | end 234 | 235 | if user 236 | log "Changing user to #{user}." 237 | Process::UID.change_privilege(Etc.getpwnam(user).uid) 238 | end 239 | rescue Errno::EPERM 240 | log "FAILED to change user:group #{user}:#{group}: #$!" 241 | exit 1 242 | end 243 | end 244 | end 245 | -------------------------------------------------------------------------------- /bin/swiftiplyctl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | require 'socket' 5 | require 'yaml' 6 | 7 | ENV['SWIFT'] = 'true' 8 | 9 | class ServerInstance 10 | attr_accessor :instances,:location,:online,:port 11 | 12 | def initialize(port,options = {}) 13 | self.instances = options.delete(:instances) 14 | self.location = options.delete(:location) 15 | self.port = port 16 | begin 17 | TCPSocket.open('127.0.0.1',self.port).close 18 | self.online = true 19 | rescue Errno::ECONNREFUSED 20 | self.online = false 21 | end 22 | end 23 | 24 | def log(msg) 25 | STDERR.print msg 26 | end 27 | end 28 | 29 | class MongrelInstance < ServerInstance 30 | attr_accessor :pid_cache 31 | 32 | def add 33 | log "Adding #{self.instances} Mongrel instance#{'s' unless self.instances == 1}\n" 34 | self.launch_instances 35 | log "\n" 36 | end 37 | 38 | def initialize(*args) 39 | super 40 | self.online = !self.pids.empty? 41 | end 42 | 43 | def launch_instances 44 | size = self.pids.size 45 | self.instances.times do |i| 46 | pid = size+i 47 | # I know we specified env['swift'] above, but hey, it never hurts to double-check 48 | `env SWIFT=1 mongrel_rails start -c #{self.location} -p #{self.port} -d -P #{File.join(self.location,'log',"dog#{pid.to_s.rjust(3,"0")}.pid")}` 49 | sleep 1 50 | log "Mongrel instance ##{pid+1} started#{"\n" unless i+1 == self.instances}" 51 | end 52 | end 53 | 54 | def pids 55 | self.pid_cache ||= Dir.glob(File.join(self.location,'log','dog*.pid')).sort 56 | end 57 | 58 | def remove 59 | log "Shaving #{self.instances} Mongrel instance#{'s' unless self.instances == 1} from the stack... " 60 | if self.pids.empty? 61 | log "No Mongrel instances!" 62 | else 63 | self.instances.times do |i| 64 | self.stop_instance(self.pids.pop) 65 | end 66 | log "Done!" 67 | end 68 | log "\n" 69 | end 70 | 71 | def start 72 | log "Starting #{self.instances} Mongrel instance#{'s' unless self.instances == 1} on port #{self.port}... " 73 | if !self.pids.empty? 74 | log 'Mongrel already started' 75 | else 76 | log "\n" 77 | self.launch_instances 78 | end 79 | log "\n" 80 | end 81 | 82 | def status 83 | log "Mongrel: #{self.online ? "\033[1;32mOnline\033[00m 84 | Instances: \033[1;32m#{self.pids.size}\033[00m" : "\033[1;31mOffline\033[00m"}\n" 85 | end 86 | 87 | def stop 88 | log 'Shutting down Mongrel... ' 89 | # We work based on pids b/c Swiftiplied Mongrel instances don't reply to TCPSocket connections for some reason 90 | if !self.pids.empty? 91 | for pid in self.pids 92 | self.stop_instance(pid) 93 | end 94 | log 'Done!' 95 | else 96 | log "No active Mongrel instance#{'s' unless self.instances == 1} on port #{self.port}" 97 | end 98 | log "\n" 99 | end 100 | 101 | def stop_instance(pid) 102 | Process.kill("KILL",File.read(pid).to_i) 103 | File.unlink(pid) 104 | sleep 1 105 | end 106 | end 107 | 108 | class SwiftiplyInstance < ServerInstance 109 | attr_accessor :pid 110 | 111 | def initialize(*args) 112 | super 113 | self.pid = `pgrep -f 'swiftiply -c #{File.expand_path(File.join(self.location,'config','swiftiply.yml'))}'` 114 | self.online = self.pid != "" 115 | end 116 | 117 | def start 118 | log "Starting Swiftiply instance on port #{self.port}... " 119 | if self.online 120 | log 'Swiftiply already started' 121 | else 122 | # Okay, Swiftiply actually switched pids on me regularly, so I'm going a different route with this one... 123 | IO.popen("swiftiply -c #{File.expand_path(File.join(self.location,'config','swiftiply.yml'))}") 124 | log 'Done!' 125 | end 126 | log "\n" 127 | end 128 | 129 | def status 130 | log "Swiftiply: #{self.online ? "\033[1;32mOnline\033[00m" : "\033[1;31mOffline\033[00m"}\n" 131 | end 132 | 133 | def stop 134 | log 'Shutting down Swiftiply... ' 135 | if self.online 136 | Process.kill("KILL",self.pid.to_i) 137 | sleep 1 138 | log 'Done!' 139 | else 140 | log "No active Swiftiply instance on port #{self.port}" 141 | end 142 | log "\n" 143 | end 144 | end 145 | 146 | class SwiftiplyControl 147 | def self.load_config 148 | if @command =~ /(status_all)/ 149 | @mongrel_pids = `pgrep -l -f 'mongrel'`.split("\n") 150 | @swift_pids = `pgrep -l -f 'swiftiply'`.split("\n") 151 | elsif File.exists?(@location) 152 | config_dir = File.join(@location,'config') 153 | yml_path = File.join(config_dir,'swiftiply.yml') 154 | if File.exists?(yml_path) 155 | yaml = YAML::load(File.open(yml_path)) 156 | @mongrel = MongrelInstance.new(yaml["map"].first["outgoing"].split(":").last.strip,:instances => (@config[:instances] || yaml["n"]).to_i,:location => @location) 157 | @swiftiply = SwiftiplyInstance.new(yaml["cluster_port"],:location => @location) 158 | else 159 | response = "" 160 | while response !~ /(Y|n)/ 161 | log "No swiftiply.yml file found in #{File.join(@dir || ".",'config')}. Would you like to generate one now? [Yn] " 162 | response = STDIN.gets.chomp 163 | end 164 | @config[:instances] = "2" unless @config[:instances].to_i > 0 165 | if response == 'Y' 166 | Dir.mkdir(config_dir) unless File.exists?(config_dir) 167 | yaml = "cluster_address: 127.0.0.1 168 | cluster_port: 3000 169 | daemonize: true 170 | epoll: true 171 | epoll_descriptors: 8192 172 | map: 173 | - incoming: localhost 174 | outgoing: 127.0.0.1:5000 175 | default: true 176 | docroot: #{@location} 177 | redeployable: true 178 | n: #{@config[:instances]}" 179 | file = File.open(yml_path,'w+') 180 | file.write(yaml) 181 | file.close 182 | self.load_config 183 | else 184 | exit! 185 | end 186 | end 187 | else 188 | log "No site found at #{@location}\n" 189 | exit! 190 | end 191 | end 192 | 193 | def self.log(msg) 194 | STDERR.print msg 195 | end 196 | 197 | def self.parse_options(config = {}) 198 | defaults = {} 199 | OptionParser.new do |opts| 200 | opts.banner = 'Usage: swiftiply_config [options]' 201 | opts.separator '' 202 | opts.on('-n','--num-mongrels [NUM]','The number of mongrels to start.') do |num| 203 | config[:instances] = num 204 | end 205 | end.parse! 206 | @config = defaults.update(config) 207 | if @command 208 | @dir = ARGV.shift 209 | @location = @dir.nil? ? Dir.pwd : @dir[0,1] == "/" ? @dir : File.join(Dir.pwd,@dir) 210 | self.load_config 211 | end 212 | end 213 | 214 | def self.run 215 | @command = ARGV.shift 216 | parse_options 217 | case @command 218 | when "add_mongrel" 219 | @mongrel.instances = (@config[:instances] || "1").to_i 220 | @mongrel.add 221 | when "remove_mongrel" 222 | @mongrel.instances = (@config[:instances] || "1").to_i 223 | @mongrel.remove 224 | when "restart" 225 | @mongrel.stop 226 | @mongrel.start 227 | @swiftiply.stop 228 | @swiftiply.start 229 | when "restart_mongrel" 230 | @mongrel.stop 231 | @mongrel.start 232 | when "restart_swift" 233 | @swiftiply.stop 234 | @swiftiply.start 235 | when "start" 236 | @mongrel.start 237 | @swiftiply.start 238 | when "start_mongrel" 239 | @mongrel.start 240 | when "start_swift" 241 | @swiftiply.start 242 | when "status" 243 | @mongrel.status 244 | @swiftiply.status 245 | when "status_all" 246 | pids = [@swift_pids.select{|pid| pid =~ /swiftiply -c (.+)$/}.collect{|pid| pid.slice(/swiftiply -c (.+)$/).gsub(/(swiftiply -c |\/config\/swiftiply\.yml)/,'')},@mongrel_pids.select{|pid| pid =~ /-c ([^ ]+) /}.collect{|pid| File.expand_path(pid.match(/-c ([^ ]+) /)[1].strip + "/")}].flatten.uniq.compact.sort 247 | for server in pids 248 | log "#{server}\n" 249 | `swift status #{server}` 250 | log "\n" 251 | end 252 | when "stop" 253 | @mongrel.stop 254 | @swiftiply.stop 255 | when "stop_mongrel" 256 | @mongrel.stop 257 | when "stop_swift" 258 | @swiftiply.stop 259 | else 260 | log "#{@command} is not a valid command\n" unless @command.nil? 261 | log "Usage: swiftiply_ctl [options] 262 | Available commands are: 263 | 264 | - add_mongrel 265 | - remove_mongrel 266 | - restart 267 | - restart_mongrel 268 | - restart_swift 269 | - start 270 | - start_mongrel 271 | - start_swift 272 | - status 273 | - stop 274 | - stop_mongrel 275 | - stop_swift 276 | 277 | Each command takes -h as an option to get help. 278 | " 279 | end 280 | end 281 | end 282 | 283 | SwiftiplyControl.run 284 | -------------------------------------------------------------------------------- /external/httpclient.rb: -------------------------------------------------------------------------------- 1 | # This is a replacement for the regular EventMachine HttpClient library. 2 | # It removes a few lines that seek to protect the user against himself. 3 | # This makes it a much more useful tool for sending wacky request lines 4 | # to a web server. 5 | 6 | 7 | module EventMachine 8 | module Protocols 9 | 10 | class HttpClient < Connection 11 | include EventMachine::Deferrable 12 | 13 | remove_const :MaxPostContentLength if MaxPostContentLength 14 | MaxPostContentLength = 20 * 1024 * 1024 15 | 16 | # USAGE SAMPLE: 17 | # 18 | # EventMachine.run { 19 | # http = EventMachine::Protocols::HttpClient.request( 20 | # :host => server, 21 | # :port => 80, 22 | # :request => "/index.html", 23 | # :query_string => "parm1=value1&parm2=value2" 24 | # ) 25 | # http.callback {|response| 26 | # puts response[:status] 27 | # puts response[:headers] 28 | # puts response[:content] 29 | # } 30 | # } 31 | # 32 | 33 | # TODO: 34 | # Add streaming so we can support enormous POSTs. Current max is 20meg. 35 | # Timeout for connections that run too long or hang somewhere in the middle. 36 | # Persistent connections (HTTP/1.1), may need a associated delegate object. 37 | # DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's 38 | # DNS lookups are unbelievably slow. 39 | # HEAD requests. 40 | # Chunked transfer encoding. 41 | # Convenience methods for requests. get, post, url, etc. 42 | # SSL. 43 | # Handle status codes like 304, 100, etc. 44 | # Refactor this code so that protocol errors all get handled one way (an exception?), 45 | # instead of sprinkling set_deferred_status :failed calls everywhere. 46 | 47 | def self.request( args = {} ) 48 | args[:port] ||= 80 49 | EventMachine.connect( args[:host], args[:port], self ) {|c| 50 | # According to the docs, we will get here AFTER post_init is called. 51 | c.instance_eval {@args = args} 52 | } 53 | end 54 | 55 | def post_init 56 | @start_time = Time.now 57 | @data = "" 58 | @read_state = :base 59 | end 60 | 61 | # We send the request when we get a connection. 62 | # AND, we set an instance variable to indicate we passed through here. 63 | # That allows #unbind to know whether there was a successful connection. 64 | # NB: This naive technique won't work when we have to support multiple 65 | # requests on a single connection. 66 | def connection_completed 67 | @connected = true 68 | send_request @args 69 | end 70 | 71 | def send_request args 72 | args[:verb] ||= args[:method] # Support :method as an alternative to :verb. 73 | args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified? 74 | 75 | verb = args[:verb].to_s.upcase 76 | unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb) 77 | set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type 78 | return # NOTE THE EARLY RETURN, we're not sending any data. 79 | end 80 | 81 | request = args[:request] || "/" 82 | # unless request[0,1] == "/" 83 | # request = "/" + request 84 | # end 85 | 86 | qs = args[:query_string] || "" 87 | if qs.length > 0 and qs[0,1] != '?' 88 | qs = "?" + qs 89 | end 90 | 91 | # Allow an override for the host header if it's not the connect-string. 92 | host = args[:host_header] || args[:host] || "_" 93 | # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default. 94 | port = args[:port] 95 | 96 | # POST items. 97 | postcontenttype = args[:contenttype] || "application/octet-stream" 98 | postcontent = args[:content] || "" 99 | raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength 100 | 101 | # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise. 102 | # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption. 103 | req = [ 104 | "#{verb} #{request}#{qs} HTTP/1.1", 105 | "Host: #{host}:#{port}", 106 | "User-agent: Ruby EventMachine", 107 | ] 108 | 109 | if verb == "POST" || verb == "PUT" 110 | req << "Content-type: #{postcontenttype}" 111 | req << "Content-length: #{postcontent.length}" 112 | end 113 | 114 | # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string. 115 | # Eventually we will want to deal intelligently with arrays and hashes. 116 | if args[:cookie] 117 | req << "Cookie: #{args[:cookie]}" 118 | end 119 | 120 | req << "" 121 | reqstring = req.map {|l| "#{l}\r\n"}.join 122 | send_data reqstring 123 | 124 | if verb == "POST" || verb == "PUT" 125 | send_data postcontent 126 | end 127 | end 128 | 129 | 130 | def receive_data data 131 | while data and data.length > 0 132 | case @read_state 133 | when :base 134 | # Perform any per-request initialization here and don't consume any data. 135 | @data = "" 136 | @headers = [] 137 | @content_length = nil # not zero 138 | @content = "" 139 | @status = nil 140 | @read_state = :header 141 | when :header 142 | ary = data.split( /\r?\n/m, 2 ) 143 | if ary.length == 2 144 | data = ary.last 145 | if ary.first == "" 146 | if @content_length and @content_length > 0 147 | @read_state = :content 148 | else 149 | dispatch_response 150 | @read_state = :base 151 | end 152 | else 153 | @headers << ary.first 154 | if @headers.length == 1 155 | parse_response_line 156 | elsif ary.first =~ /\Acontent-length:\s*/i 157 | # Only take the FIRST content-length header that appears, 158 | # which we can distinguish because @content_length is nil. 159 | # TODO, it's actually a fatal error if there is more than one 160 | # content-length header, because the caller is presumptively 161 | # a bad guy. (There is an exploit that depends on multiple 162 | # content-length headers.) 163 | @content_length ||= $'.to_i 164 | end 165 | end 166 | else 167 | @data << data 168 | data = "" 169 | end 170 | when :content 171 | # If there was no content-length header, we have to wait until the connection 172 | # closes. Everything we get until that point is content. 173 | # TODO: Must impose a content-size limit, and also must implement chunking. 174 | # Also, must support either temporary files for large content, or calling 175 | # a content-consumer block supplied by the user. 176 | if @content_length 177 | bytes_needed = @content_length - @content.length 178 | @content += data[0, bytes_needed] 179 | data = data[bytes_needed..-1] || "" 180 | if @content_length == @content.length 181 | dispatch_response 182 | @read_state = :base 183 | end 184 | else 185 | @content << data 186 | data = "" 187 | end 188 | end 189 | end 190 | end 191 | 192 | 193 | # We get called here when we have received an HTTP response line. 194 | # It's an opportunity to throw an exception or trigger other exceptional 195 | # handling. 196 | def parse_response_line 197 | if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/ 198 | @status = $1.to_i 199 | else 200 | set_deferred_status :failed, { 201 | :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this. 202 | } 203 | close_connection 204 | end 205 | end 206 | private :parse_response_line 207 | 208 | def dispatch_response 209 | @read_state = :base 210 | set_deferred_status :succeeded, { 211 | :content => @content, 212 | :headers => @headers, 213 | :status => @status 214 | } 215 | # TODO, we close the connection for now, but this is wrong for persistent clients. 216 | close_connection 217 | end 218 | 219 | def unbind 220 | if !@connected 221 | set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error. 222 | elsif (@read_state == :content and @content_length == nil) 223 | dispatch_response 224 | end 225 | end 226 | end 227 | 228 | end 229 | end 230 | 231 | 232 | -------------------------------------------------------------------------------- /bin/evented_mongrel_rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # No more mucking with event variables. Run swiftiplied_mongrel_rails to run 4 | # a mongrel_rails setup for Swiftiply. 5 | 6 | require 'rubygems' 7 | require 'yaml' 8 | 9 | require 'swiftcore/evented_mongrel' 10 | 11 | require 'mongrel/rails' 12 | require 'etc' 13 | require 'cgi_multipart_eof_fix' rescue nil 14 | 15 | module Mongrel 16 | class Start < GemPlugin::Plugin "/commands" 17 | include Mongrel::Command::Base 18 | 19 | def configure 20 | options [ 21 | ["-e", "--environment ENV", "Rails environment to run as", :@environment, ENV['RAILS_ENV'] || "development"], 22 | ["-d", "--daemonize", "Run daemonized in the background", :@daemon, false], 23 | ['-p', '--port PORT', "Which port to bind to", :@port, 3000], 24 | ['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"], 25 | ['-l', '--log FILE', "Where to write log messages", :@log_file, "log/mongrel.log"], 26 | ['-P', '--pid FILE', "Where to write the PID", :@pid_file, "log/mongrel.pid"], 27 | ['-n', '--num-procs INT', "Number of processors active before clients denied", :@num_procs, 1024], 28 | ['-t', '--timeout TIME', "Timeout all requests after 100th seconds time", :@timeout, 0], 29 | ['-m', '--mime PATH', "A YAML file that lists additional MIME types", :@mime_map, nil], 30 | ['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, Dir.pwd], 31 | ['-r', '--root PATH', "Set the document root (default 'public')", :@docroot, "public"], 32 | ['-B', '--debug', "Enable debugging mode", :@debug, false], 33 | ['-C', '--config PATH', "Use a config file", :@config_file, nil], 34 | ['-S', '--script PATH', "Load the given file as an extra config script", :@config_script, nil], 35 | ['-G', '--generate PATH', "Generate a config file for use with -C", :@generate, nil], 36 | ['', '--user USER', "User to run as", :@user, nil], 37 | ['', '--group GROUP', "Group to run as", :@group, nil], 38 | ['', '--prefix PATH', "URL prefix for Rails app", :@prefix, nil] 39 | ] 40 | end 41 | 42 | def validate 43 | @cwd = File.expand_path(@cwd) 44 | valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd" 45 | 46 | # Change there to start, then we'll have to come back after daemonize 47 | Dir.chdir(@cwd) 48 | 49 | valid?(@prefix[0].chr == "/" && @prefix[-1].chr != "/", "Prefix must begin with / and not end in /") if @prefix 50 | valid_dir? File.dirname(@log_file), "Path to log file not valid: #@log_file" 51 | valid_dir? File.dirname(@pid_file), "Path to pid file not valid: #@pid_file" 52 | valid_dir? @docroot, "Path to docroot not valid: #@docroot" 53 | valid_exists? @mime_map, "MIME mapping file does not exist: #@mime_map" if @mime_map 54 | valid_exists? @config_file, "Config file not there: #@config_file" if @config_file 55 | valid_dir? File.dirname(@generate), "Problem accessing directory to #@generate" if @generate 56 | valid_user? @user if @user 57 | valid_group? @group if @group 58 | 59 | return @valid 60 | end 61 | 62 | def run 63 | # Config file settings will override command line settings 64 | settings = { :host => @address, :port => @port, :cwd => @cwd, 65 | :log_file => @log_file, :pid_file => @pid_file, :environment => @environment, 66 | :docroot => @docroot, :mime_map => @mime_map, :daemon => @daemon, 67 | :debug => @debug, :includes => ["mongrel"], :config_script => @config_script, 68 | :num_processors => @num_procs, :timeout => @timeout, 69 | :user => @user, :group => @group, :prefix => @prefix, :config_file => @config_file 70 | } 71 | 72 | if @generate 73 | STDERR.puts "** Writing config to \"#@generate\"." 74 | open(@generate, "w") {|f| f.write(settings.to_yaml) } 75 | STDERR.puts "** Finished. Run \"mongrel_rails -C #@generate\" to use the config file." 76 | exit 0 77 | end 78 | 79 | if @config_file 80 | settings.merge! YAML.load_file(@config_file) 81 | STDERR.puts "** Loading settings from #{@config_file} (they override command line)." unless settings[:daemon] 82 | end 83 | 84 | config = Mongrel::Rails::RailsConfigurator.new(settings) do 85 | if defaults[:daemon] 86 | if File.exist? defaults[:pid_file] 87 | log "!!! PID file #{defaults[:pid_file]} already exists. Mongrel could be running already. Check your #{defaults[:log_file]} for errors." 88 | log "!!! Exiting with error. You must stop mongrel and clear the .pid before I'll attempt a start." 89 | exit 1 90 | end 91 | 92 | daemonize 93 | log "Daemonized, any open files are closed. Look at #{defaults[:pid_file]} and #{defaults[:log_file]} for info." 94 | log "Settings loaded from #{@config_file} (they override command line)." if @config_file 95 | end 96 | 97 | log "Starting Mongrel listening at #{defaults[:host]}:#{defaults[:port]}" 98 | 99 | listener do 100 | mime = {} 101 | if defaults[:mime_map] 102 | log "Loading additional MIME types from #{defaults[:mime_map]}" 103 | mime = load_mime_map(defaults[:mime_map], mime) 104 | end 105 | 106 | if defaults[:debug] 107 | log "Installing debugging prefixed filters. Look in log/mongrel_debug for the files." 108 | debug "/" 109 | end 110 | 111 | log "Starting Rails with #{defaults[:environment]} environment..." 112 | log "Mounting Rails at #{defaults[:prefix]}..." if defaults[:prefix] 113 | uri defaults[:prefix] || "/", :handler => rails(:mime => mime, :prefix => defaults[:prefix]) 114 | log "Rails loaded." 115 | 116 | log "Loading any Rails specific GemPlugins" 117 | load_plugins 118 | 119 | if defaults[:config_script] 120 | log "Loading #{defaults[:config_script]} external config script" 121 | run_config(defaults[:config_script]) 122 | end 123 | 124 | setup_rails_signals 125 | end 126 | end 127 | 128 | config.run 129 | config.log "Mongrel available at #{settings[:host]}:#{settings[:port]}" 130 | 131 | if config.defaults[:daemon] 132 | config.write_pid_file 133 | else 134 | config.log "Use CTRL-C to stop." 135 | end 136 | 137 | config.join 138 | 139 | if config.needs_restart 140 | if RUBY_PLATFORM !~ /mswin/ 141 | cmd = "ruby #{__FILE__} start #{original_args.join(' ')}" 142 | config.log "Restarting with arguments: #{cmd}" 143 | config.stop 144 | config.remove_pid_file 145 | 146 | if config.defaults[:daemon] 147 | system cmd 148 | else 149 | STDERR.puts "Can't restart unless in daemon mode." 150 | exit 1 151 | end 152 | else 153 | config.log "Win32 does not support restarts. Exiting." 154 | end 155 | end 156 | end 157 | end 158 | 159 | def Mongrel::send_signal(signal, pid_file) 160 | pid = open(pid_file).read.to_i 161 | print "Sending #{signal} to Mongrel at PID #{pid}..." 162 | begin 163 | Process.kill(signal, pid) 164 | rescue Errno::ESRCH 165 | puts "Process does not exist. Not running." 166 | end 167 | 168 | puts "Done." 169 | end 170 | 171 | 172 | class Stop < GemPlugin::Plugin "/commands" 173 | include Mongrel::Command::Base 174 | 175 | def configure 176 | options [ 177 | ['-c', '--chdir PATH', "Change to dir before starting (will be expanded).", :@cwd, "."], 178 | ['-f', '--force', "Force the shutdown (kill -9).", :@force, false], 179 | ['-w', '--wait SECONDS', "Wait SECONDS before forcing shutdown", :@wait, "0"], 180 | ['-P', '--pid FILE', "Where the PID file is located.", :@pid_file, "log/mongrel.pid"] 181 | ] 182 | end 183 | 184 | def validate 185 | @cwd = File.expand_path(@cwd) 186 | valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd" 187 | 188 | Dir.chdir @cwd 189 | 190 | valid_exists? @pid_file, "PID file #@pid_file does not exist. Not running?" 191 | return @valid 192 | end 193 | 194 | def run 195 | if @force 196 | @wait.to_i.times do |waiting| 197 | exit(0) if not File.exist? @pid_file 198 | sleep 1 199 | end 200 | 201 | Mongrel::send_signal("KILL", @pid_file) if File.exist? @pid_file 202 | else 203 | Mongrel::send_signal("TERM", @pid_file) 204 | end 205 | end 206 | end 207 | 208 | 209 | class Restart < GemPlugin::Plugin "/commands" 210 | include Mongrel::Command::Base 211 | 212 | def configure 213 | options [ 214 | ['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, '.'], 215 | ['-s', '--soft', "Do a soft restart rather than a process exit restart", :@soft, false], 216 | ['-P', '--pid FILE', "Where the PID file is located", :@pid_file, "log/mongrel.pid"] 217 | ] 218 | end 219 | 220 | def validate 221 | @cwd = File.expand_path(@cwd) 222 | valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd" 223 | 224 | Dir.chdir @cwd 225 | 226 | valid_exists? @pid_file, "PID file #@pid_file does not exist. Not running?" 227 | return @valid 228 | end 229 | 230 | def run 231 | if @soft 232 | Mongrel::send_signal("HUP", @pid_file) 233 | else 234 | Mongrel::send_signal("USR2", @pid_file) 235 | end 236 | end 237 | end 238 | end 239 | 240 | 241 | GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE 242 | 243 | 244 | if not Mongrel::Command::Registry.instance.run ARGV 245 | exit 1 246 | end 247 | -------------------------------------------------------------------------------- /bin/swiftiplied_mongrel_rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # No more mucking with event variables. Run swiftiplied_mongrel_rails to run 4 | # a mongrel_rails setup for Swiftiply. 5 | 6 | require 'rubygems' 7 | require 'yaml' 8 | 9 | require 'swiftcore/swiftiplied_mongrel' 10 | 11 | require 'mongrel/rails' 12 | require 'etc' 13 | require 'cgi_multipart_eof_fix' rescue nil 14 | 15 | module Mongrel 16 | class Start < GemPlugin::Plugin "/commands" 17 | include Mongrel::Command::Base 18 | 19 | def configure 20 | options [ 21 | ["-e", "--environment ENV", "Rails environment to run as", :@environment, ENV['RAILS_ENV'] || "development"], 22 | ["-d", "--daemonize", "Run daemonized in the background", :@daemon, false], 23 | ['-p', '--port PORT', "Which port to bind to", :@port, 3000], 24 | ['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"], 25 | ['-l', '--log FILE', "Where to write log messages", :@log_file, "log/mongrel.log"], 26 | ['-P', '--pid FILE', "Where to write the PID", :@pid_file, "log/mongrel.pid"], 27 | ['-n', '--num-procs INT', "Number of processors active before clients denied", :@num_procs, 1024], 28 | ['-t', '--timeout TIME', "Timeout all requests after 100th seconds time", :@timeout, 0], 29 | ['-m', '--mime PATH', "A YAML file that lists additional MIME types", :@mime_map, nil], 30 | ['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, Dir.pwd], 31 | ['-r', '--root PATH', "Set the document root (default 'public')", :@docroot, "public"], 32 | ['-B', '--debug', "Enable debugging mode", :@debug, false], 33 | ['-C', '--config PATH', "Use a config file", :@config_file, nil], 34 | ['-S', '--script PATH', "Load the given file as an extra config script", :@config_script, nil], 35 | ['-G', '--generate PATH', "Generate a config file for use with -C", :@generate, nil], 36 | ['', '--user USER', "User to run as", :@user, nil], 37 | ['', '--group GROUP', "Group to run as", :@group, nil], 38 | ['', '--prefix PATH', "URL prefix for Rails app", :@prefix, nil] 39 | ] 40 | end 41 | 42 | def validate 43 | @cwd = File.expand_path(@cwd) 44 | valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd" 45 | 46 | # Change there to start, then we'll have to come back after daemonize 47 | Dir.chdir(@cwd) 48 | 49 | valid?(@prefix[0].chr == "/" && @prefix[-1].chr != "/", "Prefix must begin with / and not end in /") if @prefix 50 | valid_dir? File.dirname(@log_file), "Path to log file not valid: #@log_file" 51 | valid_dir? File.dirname(@pid_file), "Path to pid file not valid: #@pid_file" 52 | valid_dir? @docroot, "Path to docroot not valid: #@docroot" 53 | valid_exists? @mime_map, "MIME mapping file does not exist: #@mime_map" if @mime_map 54 | valid_exists? @config_file, "Config file not there: #@config_file" if @config_file 55 | valid_dir? File.dirname(@generate), "Problem accessing directory to #@generate" if @generate 56 | valid_user? @user if @user 57 | valid_group? @group if @group 58 | 59 | return @valid 60 | end 61 | 62 | def run 63 | # Config file settings will override command line settings 64 | settings = { :host => @address, :port => @port, :cwd => @cwd, 65 | :log_file => @log_file, :pid_file => @pid_file, :environment => @environment, 66 | :docroot => @docroot, :mime_map => @mime_map, :daemon => @daemon, 67 | :debug => @debug, :includes => ["mongrel"], :config_script => @config_script, 68 | :num_processors => @num_procs, :timeout => @timeout, 69 | :user => @user, :group => @group, :prefix => @prefix, :config_file => @config_file 70 | } 71 | 72 | if @generate 73 | STDERR.puts "** Writing config to \"#@generate\"." 74 | open(@generate, "w") {|f| f.write(settings.to_yaml) } 75 | STDERR.puts "** Finished. Run \"mongrel_rails -C #@generate\" to use the config file." 76 | exit 0 77 | end 78 | 79 | if @config_file 80 | settings.merge! YAML.load_file(@config_file) 81 | STDERR.puts "** Loading settings from #{@config_file} (they override command line)." unless settings[:daemon] 82 | end 83 | 84 | config = Mongrel::Rails::RailsConfigurator.new(settings) do 85 | if defaults[:daemon] 86 | if File.exist? defaults[:pid_file] 87 | log "!!! PID file #{defaults[:pid_file]} already exists. Mongrel could be running already. Check your #{defaults[:log_file]} for errors." 88 | log "!!! Exiting with error. You must stop mongrel and clear the .pid before I'll attempt a start." 89 | exit 1 90 | end 91 | 92 | daemonize 93 | log "Daemonized, any open files are closed. Look at #{defaults[:pid_file]} and #{defaults[:log_file]} for info." 94 | log "Settings loaded from #{@config_file} (they override command line)." if @config_file 95 | end 96 | 97 | log "Starting Mongrel listening at #{defaults[:host]}:#{defaults[:port]}" 98 | 99 | listener do 100 | mime = {} 101 | if defaults[:mime_map] 102 | log "Loading additional MIME types from #{defaults[:mime_map]}" 103 | mime = load_mime_map(defaults[:mime_map], mime) 104 | end 105 | 106 | if defaults[:debug] 107 | log "Installing debugging prefixed filters. Look in log/mongrel_debug for the files." 108 | debug "/" 109 | end 110 | 111 | log "Starting Rails with #{defaults[:environment]} environment..." 112 | log "Mounting Rails at #{defaults[:prefix]}..." if defaults[:prefix] 113 | uri defaults[:prefix] || "/", :handler => rails(:mime => mime, :prefix => defaults[:prefix]) 114 | log "Rails loaded." 115 | 116 | log "Loading any Rails specific GemPlugins" 117 | load_plugins 118 | 119 | if defaults[:config_script] 120 | log "Loading #{defaults[:config_script]} external config script" 121 | run_config(defaults[:config_script]) 122 | end 123 | 124 | setup_rails_signals 125 | end 126 | end 127 | 128 | config.run 129 | config.log "Mongrel available at #{settings[:host]}:#{settings[:port]}" 130 | 131 | if config.defaults[:daemon] 132 | config.write_pid_file 133 | else 134 | config.log "Use CTRL-C to stop." 135 | end 136 | 137 | config.join 138 | 139 | if config.needs_restart 140 | if RUBY_PLATFORM !~ /mswin/ 141 | cmd = "ruby #{__FILE__} start #{original_args.join(' ')}" 142 | config.log "Restarting with arguments: #{cmd}" 143 | config.stop 144 | config.remove_pid_file 145 | 146 | if config.defaults[:daemon] 147 | system cmd 148 | else 149 | STDERR.puts "Can't restart unless in daemon mode." 150 | exit 1 151 | end 152 | else 153 | config.log "Win32 does not support restarts. Exiting." 154 | end 155 | end 156 | end 157 | end 158 | 159 | def Mongrel::send_signal(signal, pid_file) 160 | pid = open(pid_file).read.to_i 161 | print "Sending #{signal} to Mongrel at PID #{pid}..." 162 | begin 163 | Process.kill(signal, pid) 164 | rescue Errno::ESRCH 165 | puts "Process does not exist. Not running." 166 | end 167 | 168 | puts "Done." 169 | end 170 | 171 | 172 | class Stop < GemPlugin::Plugin "/commands" 173 | include Mongrel::Command::Base 174 | 175 | def configure 176 | options [ 177 | ['-c', '--chdir PATH', "Change to dir before starting (will be expanded).", :@cwd, "."], 178 | ['-f', '--force', "Force the shutdown (kill -9).", :@force, false], 179 | ['-w', '--wait SECONDS', "Wait SECONDS before forcing shutdown", :@wait, "0"], 180 | ['-P', '--pid FILE', "Where the PID file is located.", :@pid_file, "log/mongrel.pid"] 181 | ] 182 | end 183 | 184 | def validate 185 | @cwd = File.expand_path(@cwd) 186 | valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd" 187 | 188 | Dir.chdir @cwd 189 | 190 | valid_exists? @pid_file, "PID file #@pid_file does not exist. Not running?" 191 | return @valid 192 | end 193 | 194 | def run 195 | if @force 196 | @wait.to_i.times do |waiting| 197 | exit(0) if not File.exist? @pid_file 198 | sleep 1 199 | end 200 | 201 | Mongrel::send_signal("KILL", @pid_file) if File.exist? @pid_file 202 | else 203 | Mongrel::send_signal("TERM", @pid_file) 204 | end 205 | end 206 | end 207 | 208 | 209 | class Restart < GemPlugin::Plugin "/commands" 210 | include Mongrel::Command::Base 211 | 212 | def configure 213 | options [ 214 | ['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, '.'], 215 | ['-s', '--soft', "Do a soft restart rather than a process exit restart", :@soft, false], 216 | ['-P', '--pid FILE', "Where the PID file is located", :@pid_file, "log/mongrel.pid"] 217 | ] 218 | end 219 | 220 | def validate 221 | @cwd = File.expand_path(@cwd) 222 | valid_dir? @cwd, "Invalid path to change to during daemon mode: #@cwd" 223 | 224 | Dir.chdir @cwd 225 | 226 | valid_exists? @pid_file, "PID file #@pid_file does not exist. Not running?" 227 | return @valid 228 | end 229 | 230 | def run 231 | if @soft 232 | Mongrel::send_signal("HUP", @pid_file) 233 | else 234 | Mongrel::send_signal("USR2", @pid_file) 235 | end 236 | end 237 | end 238 | end 239 | 240 | 241 | GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE 242 | 243 | 244 | if not Mongrel::Command::Registry.instance.run ARGV 245 | exit 1 246 | end 247 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/proxy_backends/traditional.rb: -------------------------------------------------------------------------------- 1 | require 'swiftcore/Swiftiply/config' 2 | # Standard style proxy. 3 | module Swiftcore 4 | module Swiftiply 5 | module Proxies 6 | class Traditional < EventMachine::Connection 7 | Directories = { 8 | 'static' => ['swiftcore/Swiftiply/proxy_backends/traditional/static_directory.rb','::Swiftcore::Swiftiply::Proxies::TraditionalStaticDirectory'], 9 | 'redis' => ['swiftcore/Swiftiply/proxy_backends/traditional/redis_directory.rb','::Swiftcore::Swiftiply::Proxies::TraditionalRedisDirectory'] 10 | } 11 | 12 | def self.is_a_server? 13 | false 14 | end 15 | 16 | def self.parse_connection_params(config, directory) 17 | {} 18 | end 19 | 20 | # 21 | # directory: DIRECTORY_TYPE [static] 22 | # 23 | def self.config(conf, new_config) 24 | directory = nil 25 | if conf[Cdirectory] 26 | require Directories[conf[Cdirectory]].first 27 | directory = Directories[conf[Cdirectory]].last 28 | end 29 | unless directory && !directory.empty? 30 | require Directories['static'].first 31 | directory = Directories['static'].last 32 | end 33 | 34 | directory_class = Swiftcore::Swiftiply::class_by_name(directory) 35 | 36 | owners = conf[Cincoming].sort.join('|') 37 | hash = Digest::SHA256.hexdigest(owners).intern 38 | config_data = {:hash => hash, :owners => owners} 39 | 40 | Config.configure_logging(conf, config_data) 41 | file_cache = Config.configure_file_cache(conf, config_data) 42 | dynamic_request_cache = Config.configure_dynamic_request_cache(conf, config_data) 43 | etag_cache = Config.configure_etag_cache(conf, config_data) 44 | 45 | # For each incoming entry, do setup. 46 | new_config[Cincoming] = {} 47 | conf[Cincoming].each do |p_| 48 | ProxyBag.logger.log(Cinfo,"Configuring incoming #{p_}") if Swiftcore::Swiftiply::log_level > 1 49 | p = p_.intern 50 | 51 | Config.setup_caches(new_config, config_data.merge({:p => p, :file_cache => file_cache, :dynamic_request_cache => dynamic_request_cache, :etag_cache => etag_cache})) 52 | 53 | ProxyBag.add_backup_mapping(conf[Cbackup].intern,p) if conf.has_key?(Cbackup) 54 | Config.configure_docroot(conf, p) 55 | config_data[:permit_xsendfile] = Config.configure_sendfileroot(conf, p) 56 | Config.configure_xforwardedfor(conf, p) 57 | Config.configure_redeployable(conf, p) 58 | Config.configure_key(conf, p, config_data) 59 | Config.configure_staticmask(conf, p) 60 | Config.configure_cache_extensions(conf,p) 61 | Config.configure_cluster_manager(conf,p) 62 | Config.configure_backends('groups', { 63 | :config => conf, 64 | :p => p, 65 | :config_data => config_data, 66 | :new_config => new_config, 67 | :self => self, 68 | :directory_class => directory_class, 69 | :directory_args => [conf]}) 70 | Config.stop_unused_servers(new_config) 71 | # directory_class.config(conf, new_config) 72 | # Config.set_server_queue(config_data, directory_class, [conf]) 73 | end 74 | end 75 | 76 | # Here lies the protocol definition. A traditional proxy is super simple -- pass on what you get. 77 | attr_accessor :associate, :id 78 | 79 | C0rnrn = "0\r\n\r\n".freeze 80 | Crnrn = "\r\n\r\n".freeze 81 | 82 | def initialize(host=nil, port=nil) 83 | @name = self.class.bname 84 | @caching_enabled = self.class.caching 85 | @permit_xsendfile = self.class.xsendfile 86 | @enable_sendfile_404 = self.class.enable_sendfile_404 87 | @host = host 88 | @port = port 89 | super 90 | end 91 | 92 | def name 93 | @name 94 | end 95 | 96 | # Call setup() and add the backend to the ProxyBag queue. 97 | 98 | def post_init 99 | setup 100 | end 101 | 102 | # Setup the initial variables for receiving headers and content. 103 | 104 | def setup 105 | @headers = '' 106 | @headers_completed = @dont_send_data = false 107 | @content_sent = 0 108 | @filter = self.class.filter 109 | end 110 | 111 | # Receive data from the backend process. Headers are parsed from 112 | # the rest of the content. If a Content-Length header is present, 113 | # that is used to determine how much data to expect. Otherwise, 114 | # if 'Transfer-encoding: chunked' is present, assume chunked 115 | # encoding. Otherwise just read until the connection is closed. 116 | # SO MUCH functionality has to be duplicated and maintained between 117 | # here and the keepalive protocol. That funcationality need to be 118 | # refactored so that it's encapsulated better. 119 | 120 | def receive_data data 121 | unless @headers_completed 122 | if data.include?(Crnrn) 123 | @headers_completed = true 124 | h,data = data.split(/\r\n\r\n/,2) 125 | #@headers << h << Crnrn 126 | if @headers.length > 0 127 | @headers << h 128 | else 129 | @headers = h 130 | end 131 | 132 | if @headers =~ /Content-[Ll]ength: *([^\r]+)/ 133 | @content_length = $1.to_i 134 | elsif @headers =~ /Transfer-encoding: *chunked/ 135 | @content_length = nil 136 | else 137 | @content_length = nil 138 | end 139 | 140 | if @caching_enabled && @associate && @associate.request_method == CGET && @headers =~ /Etag:/ && @headers !~ /Cache-Control: *no-/ # stupid granularity -- it's on or off, only 141 | @do_caching = true 142 | @cacheable_data = '' 143 | end 144 | 145 | if @permit_xsendfile && @headers =~ /X-[Ss]endfile: *([^\r]+)/ 146 | @associate.uri = $1 147 | if ProxyBag.serve_static_file(@associate,ProxyBag.get_sendfileroot(@associate.name)) 148 | @dont_send_data = true 149 | else 150 | if @enable_sendfile_404 151 | msg = "#{@associate.uri} could not be found." 152 | @associate.send_data "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\nContent-Type: text/html\r\nContent-Length: #{msg.length}\r\n\r\n#{msg}" 153 | @associate.close_connection_after_writing 154 | @dont_send_data = true 155 | else 156 | @associate.send_data @headers + Crnrn 157 | end 158 | end 159 | else 160 | @associate.send_data @headers + Crnrn 161 | end 162 | 163 | # If keepalive is turned on, the assumption is that it will stay 164 | # on, unless the headers being returned indicate that the connection 165 | # should be closed. 166 | # So, check for a 'Connection: Closed' header. 167 | if keepalive = @associate.keepalive 168 | keepalive = false if @headers =~ /Connection: [Cc]lose/ 169 | if @associate_http_version == C1_0 170 | keepalive = false unless @headers == /Connection: Keep-Alive/i 171 | end 172 | end 173 | else 174 | @headers << data 175 | end 176 | end 177 | 178 | if @headers_completed 179 | @associate.send_data data unless @dont_send_data 180 | @cacheable_data << data if @do_caching 181 | @content_sent += data.length 182 | 183 | if @content_length and @content_sent >= @content_length or data[-6..-1] == C0rnrn 184 | # If @dont_send_data is set, then the connection is going to be closed elsewhere. 185 | unless @dont_send_data 186 | # Check to see if keepalive is enabled. 187 | if keepalive 188 | @associate.reset_state 189 | ProxyBag.remove_client(self) unless @associate 190 | else 191 | @associate.close_connection_after_writing 192 | end 193 | end 194 | self.close_connection_after_writing 195 | # add(path_info,path,data,etag,mtime,header) 196 | 197 | if @do_caching && associate_name = @associate.name 198 | ProxyBag.file_cache_map[associate_name].add(@associate.unparsed_uri, 199 | '', 200 | @cacheable_data, 201 | '', 202 | 0, 203 | @headers.scan(/^Set-Cookie:.*/).collect {|c| c =~ /: (.*)$/; $1}, 204 | @headers) 205 | ProxyBag.dynamic_request_cache[associate_name].delete(@associate.uri) 206 | end 207 | end 208 | end 209 | # TODO: Log these errors! 210 | rescue Exception => e 211 | puts "Kaboom: #{e} -- #{e.backtrace.inspect}" 212 | @associate.close_connection_after_writing if @associate 213 | @associate = nil 214 | self.close_connection_after_writing 215 | end 216 | 217 | # This is called when the backend disconnects from the proxy. 218 | 219 | def unbind 220 | associate_name = @associate.name 221 | sq = ProxyBag.server_queue(ProxyBag.incoming_mapping(associate_name)) 222 | sq && sq.requeue(associate_name, @host, @port) 223 | ProxyBag.check_for_queued_requests(@name) 224 | if @associate 225 | if !@associate.redeployable or @content_sent 226 | @associate.close_connection_after_writing 227 | else 228 | @associate.associate = nil 229 | @associate.setup_for_redeployment 230 | ProxyBag.rebind_frontend_client(@associate) 231 | end 232 | else 233 | # ProxyBag.remove_server(self) 234 | end 235 | # ProxyBag.remove_id(self) 236 | end 237 | 238 | def self.bname=(val) 239 | @bname = val 240 | end 241 | 242 | def self.bname 243 | @bname 244 | end 245 | 246 | def self.xsendfile=(val) 247 | @xsendfile = val 248 | end 249 | 250 | def self.xsendfile 251 | @xsendfile 252 | end 253 | 254 | def self.enable_sendfile_404=(val) 255 | @enable_sendfile_404 = val 256 | end 257 | 258 | def self.enable_sendfile_404 259 | @enable_sendfile_404 260 | end 261 | 262 | def self.filter=(val) 263 | @filter = val 264 | end 265 | 266 | def self.filter 267 | @filter 268 | end 269 | 270 | def filter 271 | @filter 272 | end 273 | 274 | def self.caching 275 | @caching 276 | end 277 | 278 | def self.caching=(val) 279 | @caching = val 280 | end 281 | end 282 | end 283 | end 284 | end 285 | -------------------------------------------------------------------------------- /src/swiftcore/swiftiplied_mongrel.rb: -------------------------------------------------------------------------------- 1 | # This module rewrites pieces of the very good Mongrel web server in 2 | # order to change it from a threaded application to an event based 3 | # application running inside an EventMachine event loop. It should 4 | # be compatible with the existing Mongrel handlers for Rails, 5 | # Camping, Nitro, etc.... 6 | 7 | begin 8 | load_attempted ||= false 9 | require 'eventmachine' 10 | require 'swiftcore/Swiftiply/swiftiply_client' 11 | require 'mongrel' 12 | rescue LoadError 13 | unless load_attempted 14 | load_attempted = true 15 | require 'rubygems' 16 | retry 17 | end 18 | end 19 | 20 | module Mongrel 21 | C0s = [0,0,0,0].freeze unless const_defined?(:C0s) 22 | CCCCC = 'CCCC'.freeze unless const_defined?(:CCCCC) 23 | Cblank = ''.freeze 24 | 25 | C400Header = "HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\nServer: Swiftiplied Mongrel 0.6.5\r\nConnection: close\r\n\r\n" 26 | 27 | class MongrelProtocol < SwiftiplyClientProtocol 28 | 29 | def post_init 30 | @parser = HttpParser.new 31 | @params = HttpParams.new 32 | @nparsed = 0 33 | @request = nil 34 | @request_len = nil 35 | @linebuffer = '' 36 | end 37 | 38 | def receive_data data 39 | @linebuffer << data 40 | 41 | @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished? 42 | if @parser.finished? 43 | 44 | unless @params[::Mongrel::Const::REQUEST_PATH] 45 | params[::Mongrel::Const::REQUEST_PATH] = URI.parse(params[::Mongrel::Const::REQUEST_URI]).path 46 | end 47 | 48 | unless @request_len 49 | @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i 50 | script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH]) 51 | if handlers 52 | @params[::Mongrel::Const::PATH_INFO] = path_info || Cblank # path_info shouldn't be nil, but just in case it somehow is, let's make sure we don't crash later because of it. 53 | @params[::Mongrel::Const::SCRIPT_NAME] = script_name 54 | # The previous behavior of this line set REMOTE_ADDR equal to HTTP_X_FORWARDED_FOR 55 | # if it was defined. This behavior seems inconsistent with the intention of 56 | # http://www.ietf.org/rfc/rfc3875 so it has been changed. REMOTE_ADDR now always 57 | # contains the address of the immediate source of the connection. Check 58 | # @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] if you need that information. 59 | @params[::Mongrel::Const::REMOTE_ADDR] = ::Socket.unpack_sockaddr_in(get_peername)[1] rescue nil 60 | @notifiers = handlers.select { |h| h.request_notify } 61 | end 62 | if @request_len > ::Mongrel::Const::MAX_BODY 63 | new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE) 64 | new_buffer.binmode 65 | new_buffer << @linebuffer[@nparsed..-1] 66 | @linebuffer = new_buffer 67 | else 68 | @linebuffer = StringIO.new(@linebuffer[@nparsed..-1]) 69 | @linebuffer.pos = @linebuffer.length 70 | end 71 | end 72 | if @linebuffer.length >= @request_len 73 | @linebuffer.rewind 74 | ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self) 75 | @linebuffer.delete if Tempfile === @linebuffer && FileTest.exist?(@linebuffer.path.to_s) 76 | post_init 77 | end 78 | elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER 79 | close_connection 80 | raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.") 81 | end 82 | rescue ::Mongrel::HttpParserError 83 | if $mongrel_debug_client 84 | STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!" 85 | STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n" 86 | end 87 | send_data C400Header 88 | close_connection_after_writing 89 | rescue Exception => e 90 | # This isn't a parse error; if this rescue caught an exception, then something 91 | # significant happened. 92 | raise e 93 | send_data C400Header 94 | close_connection_after_writing 95 | ensure 96 | # Make sure to cleanup the Tempfile. 97 | @linebuffer.delete if Tempfile === @linebuffer && FileTest.exist?(@linebuffer.path.to_s) 98 | end 99 | 100 | def write data 101 | send_data data 102 | end 103 | 104 | def closed? 105 | false 106 | end 107 | 108 | def flush; end # EM handles flushing, not us, so this is just here for compatibility. 109 | end 110 | 111 | class HttpServer 112 | # There is no framework agnostic way to get that key value from the 113 | # configuration into here; it'll require code specific to the way the 114 | # different frameworks handle their configuration of Mongrel. So.... 115 | # The support is here, for swiftiplied_mongrels which are secured by 116 | # a key. If someone want to donate any patches. Otherwise, this won't 117 | # really be useful to most people until 0.7.0. 118 | 119 | CHTTP_CONNECTION = 'HTTP_CONNECTION'.freeze 120 | CHTTP_VERSION = 'HTTP_VERSION'.freeze 121 | CKEEP_ALIVE = 'KEEP_ALIVE'.freeze 122 | C1_1 = '1.1'.freeze 123 | 124 | def initialize(host, port, num_processors=950, x=0, y=nil,key='') # Deal with Mongrel 1.0.1 or earlier, as well as later. 125 | @socket = nil 126 | @classifier = URIClassifier.new 127 | @host = host 128 | @port = port 129 | @key = key 130 | @workers = ThreadGroup.new 131 | if y 132 | @throttle = x 133 | @timeout = y || 60 134 | else 135 | @timeout = x 136 | end 137 | @num_processors = num_processors 138 | @death_time = 60 139 | self.class.const_set(:Instance,self) 140 | end 141 | 142 | def run 143 | trap('INT') { EventMachine.stop_event_loop } 144 | trap('TERM') { EventMachine.stop_event_loop } 145 | #@acceptor = Thread.new do 146 | @acceptor = Thread.current 147 | EventMachine.run do 148 | EM.set_timer_quantum(5) 149 | begin 150 | MongrelProtocol.connect(@host,@port.to_i,@key) 151 | rescue StopServer 152 | EventMachine.stop_event_loop 153 | end 154 | end 155 | #end 156 | end 157 | 158 | def process_http_request(params,linebuffer,client) 159 | if not params[Const::REQUEST_PATH] 160 | uri = URI.parse(params[Const::REQUEST_URI]) 161 | params[Const::REQUEST_PATH] = uri.path 162 | end 163 | 164 | raise "No REQUEST PATH" if not params[Const::REQUEST_PATH] 165 | 166 | script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH]) 167 | 168 | if handlers 169 | notifiers = handlers.select { |h| h.request_notify } 170 | request = HttpRequest.new(params, linebuffer, notifiers) 171 | 172 | http_version = params[CHTTP_VERSION] 173 | if http_version == C1_1 174 | keep_alive = params[CHTTP_CONNECTION] =~ /close/i ? false : true 175 | else 176 | keep_alive = params[CHTTP_CONNECTION] =~ /alive/i ? true : false 177 | end 178 | 179 | # request is good so far, continue processing the response 180 | response = HttpResponse.new(client,http_version,keep_alive) 181 | 182 | # Process each handler in registered order until we run out or one finalizes the response. 183 | dispatch_to_handlers(handlers,request,response) 184 | 185 | # And finally, if nobody closed the response off, we finalize it. 186 | unless response.done 187 | response.finished 188 | end 189 | else 190 | # Didn't find it, return a stock 404 response. 191 | # This code is changed from the Mongrel behavior because a content-length 192 | # header MUST accompany all HTTP responses that go into a swiftiply 193 | # keepalive connection, so just use the Response object to construct the 194 | # 404 response. 195 | response = HttpResponse.new(client) 196 | response.status = 404 197 | response.body << "#{params[Const::REQUEST_PATH]} not found" 198 | response.finished 199 | end 200 | end 201 | 202 | def dispatch_to_handlers(handlers,request,response) 203 | handlers.each do |handler| 204 | handler.process(request, response) 205 | break if response.done 206 | end 207 | end 208 | 209 | end 210 | 211 | class HttpRequest 212 | def initialize(params, linebuffer, dispatchers) 213 | @params = params 214 | @dispatchers = dispatchers 215 | @body = linebuffer 216 | end 217 | end 218 | 219 | class HttpResponse 220 | 221 | CContentLength = 'Content-Length'.freeze 222 | C1_1 = '1.1'.freeze 223 | 224 | def initialize(socket,http_version = C1_1, keepalive = false) 225 | @socket = socket 226 | @body = StringIO.new 227 | @status = 404 228 | @reason = nil 229 | @header = HeaderOut.new(StringIO.new) 230 | @header[Const::DATE] = Time.now.httpdate 231 | @body_sent = false 232 | @header_sent = false 233 | @status_sent = false 234 | @http_version = http_version 235 | #@keepalive = keepalive 236 | @keepalive = false ## DISABLE ALL KEEPALIVE 237 | end 238 | 239 | def send_file(path, small_file = false) 240 | File.open(path, "rb") do |f| 241 | while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0 242 | begin 243 | write(chunk) 244 | rescue Object => exc 245 | break 246 | end 247 | end 248 | end 249 | @body_sent = true 250 | end 251 | 252 | def send_status(content_length=@body.length) 253 | unless @status_sent 254 | @header[CContentLength] = content_length if content_length && @status != 304 255 | if @keepalive 256 | write("HTTP/1.1 #{@status} #{@reason || HTTP_STATUS_CODES[@status]}\r\nConnection: Keep-Alive\r\n") 257 | else 258 | write("HTTP/1.1 #{@status} #{@reason || HTTP_STATUS_CODES[@status]}\r\nConnection: Close\r\n") 259 | end 260 | @status_sent = true 261 | end 262 | end 263 | 264 | def write(data) 265 | @socket.send_data data 266 | end 267 | 268 | def socket_error(details) 269 | @socket.close_connection 270 | done = true 271 | raise details 272 | end 273 | 274 | def finished 275 | send_status 276 | send_header 277 | send_body 278 | end 279 | end 280 | 281 | class Configurator 282 | # This version fixes a bug in the regular Mongrel version by adding 283 | # initialization of groups. 284 | def change_privilege(user, group) 285 | if user and group 286 | log "Initializing groups for {#user}:{#group}." 287 | Process.initgroups(user,Etc.getgrnam(group).gid) 288 | end 289 | 290 | if group 291 | log "Changing group to #{group}." 292 | Process::GID.change_privilege(Etc.getgrnam(group).gid) 293 | end 294 | 295 | if user 296 | log "Changing user to #{user}." 297 | Process::UID.change_privilege(Etc.getpwnam(user).uid) 298 | end 299 | rescue Errno::EPERM 300 | log "FAILED to change user:group #{user}:#{group}: #$!" 301 | exit 1 302 | end 303 | end 304 | end 305 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/http_recognizer.rb: -------------------------------------------------------------------------------- 1 | # Encoding:ascii-8bit 2 | 3 | require 'cgi' 4 | require "swiftcore/Swiftiply/constants" 5 | 6 | module Swiftcore 7 | module Swiftiply 8 | class NotImplemented < Exception; end 9 | 10 | # This module implements the HTTP handling code. I call it a recognizer, 11 | # and not a parser because it does not parse HTTP. It is much simpler than 12 | # that, being designed only to recognize certain useful bits very quickly. 13 | 14 | class HttpRecognizer < EventMachine::Connection 15 | 16 | attr_accessor :create_time, :last_action_time, :uri, :unparsed_uri, :associate, :name, :redeployable, :data_pos, :data_len, :peer_ip, :connection_header, :keepalive, :header_data 17 | 18 | Crn = "\r\n".freeze 19 | Crnrn = "\r\n\r\n".freeze 20 | C_blank = ''.freeze 21 | C_percent = '%'.freeze 22 | Cunknown_host = 'unknown host'.freeze 23 | C200Header = "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n" 24 | C404Header = "HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n" 25 | C400Header = "HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n" 26 | 27 | def self.proxy_bag_class_is klass 28 | const_set(:ProxyBag, klass) 29 | end 30 | 31 | def self.init_class_variables 32 | @count_404 = 0 33 | @count_400 = 0 34 | end 35 | 36 | def self.increment_404_count 37 | @count_404 += 1 38 | end 39 | 40 | def self.increment_400_count 41 | @count_400 += 1 42 | end 43 | 44 | # Initialize the @data array, which is the temporary storage for blocks 45 | # of data received from the web browser client, then invoke the superclass 46 | # initialization. 47 | 48 | def initialize *args 49 | @data = Deque.new 50 | @data_pos = 0 51 | @connection_header = C_empty 52 | @header_missing_pieces = @name = @uri = @unparsed_uri = @http_version = @request_method = @none_match = @done_parsing = @header_data = nil 53 | @keepalive = true 54 | @klass = self.class 55 | super 56 | end 57 | 58 | def reset_state 59 | @data.clear 60 | @data_pos = 0 61 | @connection_header = C_empty 62 | @header_missing_pieces = @name = @uri = @unparsed_uri = @http_version = @request_method = @none_match = @done_parsing = @header_data = nil 63 | @keepalive = true 64 | end 65 | 66 | # States: 67 | # uri 68 | # name 69 | # \r\n\r\n 70 | # If-None-Match 71 | # Done Parsing 72 | def receive_data data 73 | if @done_parsing 74 | @data.unshift data 75 | push 76 | else 77 | unless @uri 78 | # It's amazing how, when writing the code, the brain can be in a zone 79 | # where line noise like this regexp makes perfect sense, and is clear 80 | # as day; one looks at it and it reads like a sentence. Then, one 81 | # comes back to it later, and looks at it when the brain is in a 82 | # different zone, and 'lo! It looks like line noise again. 83 | # 84 | # data =~ /^(\w+) +(?:\w+:\/\/([^\/]+))?([^ \?]+)\S* +HTTP\/(\d\.\d)/ 85 | # 86 | # In case it looks like line noise to you, dear reader, too: 87 | # 88 | # 1) Match and save the first set of word characters. 89 | # 90 | # Followed by one or more spaces. 91 | # 92 | # Match but do not save the word characters followed by :// 93 | # 94 | # 2) Match and save one or more characters that are not a slash 95 | # 96 | # And allow this whole thing to match 1 or 0 times. 97 | # 98 | # 3) Match and save one or more characters that are not a question 99 | # mark or a space. 100 | # 101 | # Match zero or more non-whitespace characters, followed by one 102 | # or more spaces, followed by "HTTP/". 103 | # 104 | # 4) Match and save a digit dot digit. 105 | # 106 | # Thus, this pattern will match both the standard: 107 | # GET /bar HTTP/1.1 108 | # style request, as well as the valid (for a proxy) but less common: 109 | # GET http://foo/bar HTTP/1.0 110 | # 111 | # If the match fails, then this is a bad request, and an appropriate 112 | # response will be returned. 113 | # 114 | # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec5.1.2 115 | # 116 | if data =~ /^(\w+) +(?:\w+:\/\/([^ \/]+))?(([^ \?\#]*)\S*) +HTTP\/(\d\.\d)/ 117 | @request_method = $1 118 | @unparsed_uri = $3 119 | @uri = $4 120 | @http_version = $5 121 | if $2 122 | @name = $2.intern 123 | @uri = C_slash if @uri.empty? 124 | # Rewrite the request to get rid of the http://foo portion. 125 | 126 | data.sub!(/^\w+ +\w+:\/\/[^ \/]+([^ \?]*)/,"#{@request_method} #{@uri}") 127 | end 128 | @uri = @uri.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) {[$1.delete(C_percent)].pack('H*')} if @uri.include?(C_percent) 129 | else 130 | send_400_response 131 | return 132 | end 133 | end 134 | unless @name 135 | if data =~ /^Host: *([^\r\0:]+)/ 136 | @name = $1.intern 137 | end 138 | end 139 | if @header_missing_pieces 140 | # Hopefully this doesn't happen often. 141 | d = @data.to_s << data 142 | else 143 | d = data 144 | end 145 | if d.include?(Crnrn) 146 | @name = ProxyBag.default_name unless ProxyBag.incoming_mapping(@name) 147 | if d =~ /If-None-Match: *([^\r]+)/ 148 | @none_match = $1 149 | end 150 | @header_data = d.scan(/Cookie:.*/).collect {|c| c =~ /: ([^\r]*)/; $1} 151 | @done_parsing = true 152 | 153 | # Keep-Alive works differently on HTTP 1.0 versus HTTP 1.1 154 | # HTTP 1.0 was not written to support Keep-Alive initially; it was 155 | # bolted on. Thus, for an HTTP 1.0 client to indicate that it 156 | # wants to initiate a Keep-Alive session, it must send a header: 157 | # 158 | # Connection: Keep-Alive 159 | # 160 | # Then, when the server sends the response, it must likewise add: 161 | # 162 | # Connection: Keep-Alive 163 | # 164 | # to the response. 165 | # 166 | # For HTTP 1.1, Keep-Alive is assumed. If a client does not want 167 | # Keep-Alive, then it must send the following header: 168 | # 169 | # Connection: close 170 | # 171 | # Likewise, if the server does not want to keep the connection 172 | # alive, it must send the same header: 173 | # 174 | # Connection: close 175 | # 176 | # to the client. 177 | 178 | if @name 179 | unless ProxyBag.keepalive(@name) == false 180 | if @http_version == C1_0 181 | if data =~ /Connection: Keep-Alive/i 182 | # Nonstandard HTTP 1.0 situation; apply keepalive header. 183 | @connection_header = CConnection_KeepAlive 184 | else 185 | # Standard HTTP 1.0 situation; connection will be closed. 186 | @keepalive = false 187 | @connection_header = CConnection_close 188 | end 189 | else # The connection is an HTTP 1.1 connection. 190 | if data =~ /Connection: [Cc]lose/ 191 | # Nonstandard HTTP 1.1 situation; connection will be closed. 192 | @keepalive = false 193 | end 194 | end 195 | end 196 | 197 | # THIS IS BROKEN; the interaction of @data, data, and add_frontend_client needs to be revised 198 | ProxyBag.add_frontend_client(self,@data,data) 199 | elsif @uri == ProxyBag.health_check_uri 200 | send_healthcheck_response 201 | else 202 | send_404_response 203 | end 204 | else 205 | @data.unshift data 206 | @header_missing_pieces = true 207 | end 208 | end 209 | end 210 | 211 | # Hardcoded 400 response that is sent if the request is malformed. 212 | 213 | def send_400_response 214 | ip = Socket::unpack_sockaddr_in(get_peername).last rescue Cunknown_host 215 | error = "The request received on #{ProxyBag.now.asctime} from #{ip} was malformed and could not be serviced." 216 | send_data "#{C400Header}Bad Request\n\n#{error}" 217 | ProxyBag.logger.log(Cinfo,"Bad Request -- #{error}") 218 | close_connection_after_writing 219 | increment_400_count 220 | end 221 | 222 | # Hardcoded 404 response. This is sent if a request can't be matched to 223 | # any defined incoming section. 224 | 225 | def send_404_response 226 | ip = Socket::unpack_sockaddr_in(get_peername).last rescue Cunknown_host 227 | error = "The request (#{ CGI::escapeHTML( @uri ) } --> #{@name}), received on #{ProxyBag.now.asctime} from #{ip} did not match any resource know to this server." 228 | send_data "#{C404Header}Resource not found.\n\n#{error}" 229 | ProxyBag.logger.log(Cinfo,"Resource not found -- #{error}") 230 | close_connection_after_writing 231 | increment_404_count 232 | end 233 | 234 | def send_healthcheck_response 235 | ip = Socket::unpack_sockaddr_in(get_peername).last rescue Cunknown_host 236 | message = "Health request from #{ip} at #{ProxyBag.now.asctime}\n400:#{@count_400}\n404:#{@count_404}\n\n" 237 | send_data "#{C200Header}#{message}" 238 | ProxyBag.logger.log(Cinfo,"healthcheck from ##{ip}") 239 | close_connection_after_writing 240 | end 241 | 242 | # The push method pushes data from the HttpRecognizer to whatever 243 | # entity is responsible for handling it. You MUST override this with 244 | # something useful. 245 | 246 | def push 247 | raise NotImplemented 248 | end 249 | 250 | # The connection with the web browser client has been closed, so the 251 | # object must be removed from the ProxyBag's queue if it is has not 252 | # been associated with a backend. If it has already been associated 253 | # with a backend, then it will not be in the queue and need not be 254 | # removed. 255 | 256 | def unbind 257 | ProxyBag.remove_client(self) unless @associate 258 | end 259 | 260 | def request_method; @request_method; end 261 | def http_version; @http_version; end 262 | def none_match; @none_match; end 263 | 264 | def setup_for_redeployment 265 | @data_pos = 0 266 | end 267 | 268 | def increment_404_count 269 | @klass.increment_404_count 270 | end 271 | 272 | def increment_400_count 273 | @klass.increment_400_count 274 | end 275 | 276 | end 277 | end 278 | end 279 | -------------------------------------------------------------------------------- /src/swiftcore/Swiftiply/proxy_backends/keepalive.rb: -------------------------------------------------------------------------------- 1 | require 'swiftcore/Swiftiply/config' 2 | # Standard style proxy. 3 | module Swiftcore 4 | module Swiftiply 5 | module Proxies 6 | class Keepalive < EventMachine::Connection 7 | 8 | def self.is_a_server? 9 | true 10 | end 11 | 12 | # While the configuration parsing now lives with the protocol, a lot of the code for it is generic. Those generic pieces need to live back up in Swiftcore::Swiftiply or maybe Swiftcore::Swiftiply::Config. 13 | def self.config(m,new_config) 14 | #require 'swiftcore/Swiftiply/backend_protocol' 15 | # keepalive requests are standard Swiftiply requests. 16 | 17 | # The hash of the "outgoing" config section. It is used to 18 | # uniquely identify a section. 19 | owners = m[Cincoming].sort.join('|') 20 | hash = Digest::SHA256.hexdigest(owners).intern 21 | config_data = {:hash => hash, :owners => owners} 22 | 23 | Config.configure_logging(m, config_data) 24 | file_cache = Config.configure_file_cache(m, config_data) 25 | dynamic_request_cache = Config.configure_dynamic_request_cache(m, config_data) 26 | etag_cache = Config.configure_etag_cache(m, config_data) 27 | 28 | # For each incoming entry, do setup. 29 | new_config[Cincoming] = {} 30 | m[Cincoming].each do |p_| 31 | configure_one({ :p_ => p_, 32 | :m => m, 33 | :new_config => new_config, 34 | :config_data => config_data, 35 | :file_cache => file_cache, 36 | :dynamic_request_cache => dynamic_request_cache, 37 | :etag_cache => etag_cache }) 38 | end 39 | end 40 | 41 | def self.configure_one(args = {}) 42 | new_config = args[:new_config] 43 | config_data = args[:config_data] 44 | p_ = args[:p_] 45 | file_cache = args[:file_cache] 46 | dynamic_request_cache = args[:dynamic_request_cache] 47 | etag_cache = args[:etag_cache] 48 | m = args[:m] 49 | 50 | ProxyBag.logger.log(Cinfo,"Configuring incoming #{p_}") if Swiftcore::Swiftiply::log_level > 1 51 | p = p_.intern 52 | 53 | Config.setup_caches(new_config, config_data.merge({:p => p, :file_cache => file_cache, :dynamic_request_cache => dynamic_request_cache, :etag_cache => etag_cache})) 54 | 55 | ProxyBag.add_backup_mapping(m[Cbackup].intern,p) if m.has_key?(Cbackup) 56 | Config.configure_docroot(m, p) 57 | config_data[:permit_xsendfile] = Config.configure_sendfileroot(m, p) 58 | Config.configure_xforwardedfor(m, p) 59 | Config.configure_redeployable(m, p) 60 | Config.configure_key(m, p, config_data) 61 | Config.configure_staticmask(m, p) 62 | Config.configure_cache_extensions(m,p) 63 | Config.configure_cluster_manager(m,p) 64 | Config.configure_backends(Coutgoing, { :config => m, 65 | :p => p, 66 | :config_data => config_data, 67 | :new_config => new_config, 68 | :self => self, 69 | :directory_class => ::Swiftcore::Deque, 70 | :directory_args => []}) 71 | Config.stop_unused_servers(new_config) 72 | Config.set_server_queue(config_data, ::Swiftcore::Deque, []) 73 | end 74 | 75 | # Swiftcore::Swiftiply::Proxies::Keepalive is the EventMachine::Connection 76 | # subclass that handles the communications between Swiftiply and a 77 | # persistently connected Swiftiply client process. 78 | 79 | attr_accessor :associate, :id 80 | 81 | C0rnrn = "0\r\n\r\n".freeze 82 | Crnrn = "\r\n\r\n".freeze 83 | 84 | def initialize *args 85 | @name = self.class.bname 86 | @permit_xsendfile = self.class.xsendfile 87 | @enable_sendfile_404 = self.class.enable_sendfile_404 88 | super 89 | end 90 | 91 | def name 92 | @name 93 | end 94 | 95 | # Call setup() and add the backend to the ProxyBag queue. 96 | 97 | def post_init 98 | setup 99 | @initialized = nil 100 | ProxyBag.add_server self 101 | end 102 | 103 | # Setup the initial variables for receiving headers and content. 104 | 105 | def setup 106 | @headers = '' 107 | @headers_completed = @dont_send_data = false 108 | #@content_length = nil 109 | @content_sent = 0 110 | @filter = self.class.filter 111 | end 112 | 113 | # Receive data from the backend process. Headers are parsed from 114 | # the rest of the content. If a Content-Length header is present, 115 | # that is used to determine how much data to expect. Otherwise, 116 | # if 'Transfer-encoding: chunked' is present, assume chunked 117 | # encoding. Otherwise be paranoid; something isn't the way we like 118 | # it to be. 119 | 120 | def receive_data data 121 | unless @initialized 122 | # preamble = data.slice!(0..24) 123 | preamble = data[0..24] 124 | data = data[25..-1] || C_empty 125 | keylen = preamble[23..24].to_i(16) 126 | keylen = 0 if keylen < 0 127 | key = keylen > 0 ? data.slice!(0..(keylen - 1)) : C_empty 128 | #if preamble[0..10] == Cswiftclient and key == ProxyBag.get_key(@name) 129 | if preamble.index(Cswiftclient) == 0 and key == ProxyBag.get_key(@name) 130 | @id = preamble[11..22] 131 | ProxyBag.add_id(self,@id) 132 | @initialized = true 133 | else 134 | # The worker that connected did not present the proper authentication, 135 | # so something is fishy; time to cut bait. 136 | close_connection 137 | return 138 | end 139 | end 140 | 141 | unless @headers_completed 142 | if data.include?(Crnrn) 143 | @headers_completed = true 144 | h,data = data.split(/\r\n\r\n/,2) 145 | #@headers << h << Crnrn 146 | if @headers.length > 0 147 | @headers << h 148 | else 149 | @headers = h 150 | end 151 | 152 | if @headers =~ /Content-[Ll]ength: *([^\r]+)/ 153 | @content_length = $1.to_i 154 | elsif @headers =~ /Transfer-encoding:\s*chunked/ 155 | @content_length = nil 156 | else 157 | @content_length = 0 158 | end 159 | 160 | if @permit_xsendfile && @headers =~ /X-[Ss]endfile: *([^\r]+)/ 161 | @associate.uri = $1 162 | if ProxyBag.serve_static_file(@associate,ProxyBag.get_sendfileroot(@associate.name)) 163 | @dont_send_data = true 164 | else 165 | if @enable_sendfile_404 166 | msg = "#{@associate.uri} could not be found." 167 | @associate.send_data "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\nContent-Type: text/html\r\nContent-Length: #{msg.length}\r\n\r\n#{msg}" 168 | @associate.close_connection_after_writing 169 | @dont_send_data = true 170 | else 171 | @associate.send_data @headers + Crnrn 172 | end 173 | end 174 | else 175 | @associate.send_data @headers + Crnrn 176 | end 177 | 178 | # If keepalive is turned on, the assumption is that it will stay 179 | # on, unless the headers being returned indicate that the connection 180 | # should be closed. 181 | # So, check for a 'Connection: Closed' header. 182 | if keepalive = @associate.keepalive 183 | keepalive = false if @headers =~ /Connection: [Cc]lose/ 184 | if @associate_http_version == C1_0 185 | keepalive = false unless @headers == /Connection: Keep-Alive/i 186 | end 187 | end 188 | else 189 | @headers << data 190 | end 191 | end 192 | 193 | if @headers_completed 194 | @associate.send_data data unless @dont_send_data 195 | @content_sent += data.length 196 | if ( @content_length and @content_sent >= @content_length ) or data[-6..-1] == C0rnrn or @associate.request_method == CHEAD 197 | # If @dont_send_data is set, then the connection is going to be closed elsewhere. 198 | unless @dont_send_data 199 | # Check to see if keepalive is enabled. 200 | if keepalive 201 | @associate.reset_state 202 | ProxyBag.remove_client(self) unless @associate 203 | else 204 | @associate.close_connection_after_writing 205 | end 206 | end 207 | @associate = @headers_completed = @dont_send_data = nil 208 | @headers = '' 209 | #@headers_completed = false 210 | #@content_length = nil 211 | @content_sent = 0 212 | #setup 213 | ProxyBag.add_server self 214 | end 215 | end 216 | # TODO: Log these errors! 217 | rescue Exception => e 218 | puts "Kaboom: #{e} -- #{e.backtrace.inspect}" 219 | @associate.close_connection_after_writing if @associate 220 | @associate = nil 221 | setup 222 | ProxyBag.add_server self 223 | end 224 | 225 | # This is called when the backend disconnects from the proxy. 226 | # If the backend is currently associated with a web browser client, 227 | # that connection will be closed. Otherwise, the backend will be 228 | # removed from the ProxyBag's backend queue. 229 | 230 | def unbind 231 | if @associate 232 | if !@associate.redeployable or @content_length 233 | @associate.close_connection_after_writing 234 | else 235 | @associate.associate = nil 236 | @associate.setup_for_redeployment 237 | ProxyBag.rebind_frontend_client(@associate) 238 | end 239 | else 240 | ProxyBag.remove_server(self) 241 | end 242 | ProxyBag.remove_id(self) 243 | end 244 | 245 | def self.bname=(val) 246 | @bname = val 247 | end 248 | 249 | def self.bname 250 | @bname 251 | end 252 | 253 | def self.xsendfile=(val) 254 | @xsendfile = val 255 | end 256 | 257 | def self.xsendfile 258 | @xsendfile 259 | end 260 | 261 | def self.enable_sendfile_404=(val) 262 | @enable_sendfile_404 = val 263 | end 264 | 265 | def self.enable_sendfile_404 266 | @enable_sendfile_404 267 | end 268 | 269 | def self.filter=(val) 270 | @filter = val 271 | end 272 | 273 | def self.filter 274 | @filter 275 | end 276 | 277 | def filter 278 | @filter 279 | end 280 | 281 | end 282 | end 283 | end 284 | end 285 | -------------------------------------------------------------------------------- /ext/deque/swiftcore/rubymain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace std; 5 | 6 | #include 7 | 8 | static VALUE SwiftcoreModule; 9 | static VALUE DequeClass; 10 | 11 | 12 | static void deque_mark (deque* dq) 13 | { 14 | deque::iterator q_iterator; 15 | if (dq) { 16 | for ( q_iterator = (*dq).begin(); q_iterator != (*dq).end(); q_iterator++ ) { 17 | rb_gc_mark(*q_iterator); 18 | } 19 | } 20 | } 21 | 22 | static void deque_free (deque* dq) 23 | { 24 | if (dq) 25 | delete dq; 26 | } 27 | 28 | static VALUE deque_new (VALUE self) 29 | { 30 | deque* dq = new deque; 31 | VALUE v = Data_Wrap_Struct (DequeClass, deque_mark, deque_free, dq); 32 | return v; 33 | } 34 | 35 | static VALUE deque_push_front (VALUE self, VALUE obj) 36 | { 37 | deque* dq = NULL; 38 | Data_Get_Struct (self, deque, dq); 39 | if (!dq) 40 | rb_raise (rb_eException, "No Deque Object"); 41 | 42 | (*dq).push_front(obj); 43 | 44 | return self; 45 | } 46 | 47 | static VALUE deque_pop_front (VALUE self) 48 | { 49 | deque* dq = NULL; 50 | Data_Get_Struct(self, deque, dq); 51 | if (!dq) 52 | rb_raise (rb_eException, "No Deque Object"); 53 | 54 | if ((*dq).empty()) { 55 | return Qnil; 56 | } else { 57 | VALUE r = (*dq).front(); 58 | (*dq).pop_front(); 59 | return r; 60 | } 61 | } 62 | 63 | static VALUE deque_push_back (VALUE self, VALUE obj) 64 | { 65 | deque* dq = NULL; 66 | Data_Get_Struct (self, deque, dq); 67 | if (!dq) 68 | rb_raise (rb_eException, "No Deque Object"); 69 | 70 | (*dq).push_back(obj); 71 | 72 | return self; 73 | } 74 | 75 | static VALUE deque_pop_back (VALUE self) 76 | { 77 | deque* dq = NULL; 78 | Data_Get_Struct(self, deque, dq); 79 | if (!dq) 80 | rb_raise (rb_eException, "No Deque Object"); 81 | 82 | if ((*dq).empty()) { 83 | return Qnil; 84 | } else { 85 | VALUE r = (*dq).back(); 86 | (*dq).pop_back(); 87 | return r; 88 | } 89 | } 90 | 91 | static VALUE deque_size (VALUE self) 92 | { 93 | deque* dq = NULL; 94 | Data_Get_Struct(self, deque, dq); 95 | if (!dq) 96 | rb_raise (rb_eException, "No Deque Object"); 97 | 98 | return INT2NUM((*dq).size()); 99 | } 100 | 101 | static VALUE deque_max_size (VALUE self) 102 | { 103 | deque* dq = NULL; 104 | Data_Get_Struct(self, deque, dq); 105 | if (!dq) 106 | rb_raise (rb_eException, "No Deque Object"); 107 | 108 | return INT2NUM((*dq).max_size()); 109 | } 110 | 111 | static VALUE deque_clear (VALUE self) 112 | { 113 | deque* dq = NULL; 114 | Data_Get_Struct(self, deque, dq); 115 | if (!dq) 116 | rb_raise (rb_eException, "No Deque Object"); 117 | 118 | (*dq).clear(); 119 | return self; 120 | } 121 | 122 | static VALUE deque_empty (VALUE self) 123 | { 124 | deque* dq = NULL; 125 | Data_Get_Struct(self, deque, dq); 126 | if (!dq) 127 | rb_raise (rb_eException, "No Deque Object"); 128 | 129 | if ((*dq).empty()) { 130 | return Qtrue; 131 | } else { 132 | return Qfalse; 133 | } 134 | } 135 | 136 | static VALUE deque_to_s (VALUE self) 137 | { 138 | VALUE s = rb_str_new2(""); 139 | ID to_s = rb_intern("to_s"); 140 | ID concat = rb_intern("concat"); 141 | 142 | deque* dq = NULL; 143 | deque::iterator q_iterator; 144 | Data_Get_Struct(self, deque, dq); 145 | if (!dq) 146 | rb_raise (rb_eException, "No Deque Object"); 147 | 148 | /* 149 | for ( q_iterator = (*dq).begin(); q_iterator != (*dq).end(); q_iterator++ ) { 150 | rb_funcall(s,concat,1,rb_funcall(*q_iterator,to_s,0)); 151 | } 152 | */ 153 | 154 | for ( q_iterator = (*dq).begin(); q_iterator != (*dq).end(); q_iterator++ ) { 155 | s = rb_str_concat(s,rb_funcall(*q_iterator, to_s, 0)); 156 | } 157 | return rb_str_to_str(s); 158 | } 159 | 160 | /* 161 | static VALUE deque_join (VALUE self, VALUE delimiter) 162 | { 163 | VALUE s = rb_str_new2(""); 164 | ID to_s = rb_intern("to_s"); 165 | ID concat = rb_intern("concat"); 166 | 167 | deque* dq = NULL; 168 | deque::iterator q_iterator; 169 | Data_Get_Struct(self, deque, dq); 170 | if (!dq) 171 | rb_raise (rb_eException, "No Deque Object"); 172 | 173 | for ( q_iterator = (*dq).begin(); q_iterator != (*dq).end(); q_iterator++ ) { 174 | rb_funcall(s,concat,1,rb_funcall(*q_iterator,to_s,0)); 175 | } 176 | 177 | return rb_str_to_str(s); 178 | } 179 | */ 180 | static VALUE deque_to_a (VALUE self) 181 | { 182 | VALUE ary = rb_ary_new(); 183 | ID push = rb_intern("push"); 184 | 185 | deque* dq = NULL; 186 | deque::iterator q_iterator; 187 | Data_Get_Struct(self, deque, dq); 188 | if (!dq) 189 | rb_raise (rb_eException, "No Deque Object"); 190 | 191 | for ( q_iterator = (*dq).begin(); q_iterator != (*dq).end(); q_iterator++ ) { 192 | //rb_funcall(ary,push,1,*q_iterator); 193 | rb_ary_push(ary,*q_iterator); 194 | } 195 | 196 | return ary; 197 | } 198 | 199 | static VALUE deque_inspect (VALUE self) 200 | { 201 | VALUE s = rb_str_new2("["); 202 | VALUE comma = rb_str_new2(","); 203 | ID inspect = rb_intern("inspect"); 204 | ID concat = rb_intern("concat"); 205 | 206 | deque* dq = NULL; 207 | deque::iterator q_iterator; 208 | deque::iterator last_q_iterator; 209 | Data_Get_Struct(self, deque, dq); 210 | if (!dq) 211 | rb_raise (rb_eException, "No Deque Object"); 212 | 213 | last_q_iterator = (*dq).end(); 214 | 215 | for ( q_iterator = (*dq).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 216 | // rb_funcall(s,concat,1,rb_funcall(*q_iterator,inspect,0)); 217 | rb_str_concat(s,rb_funcall(*q_iterator,inspect,0)); 218 | if (q_iterator != (last_q_iterator - 1)) 219 | // rb_funcall(s,concat,1,comma); 220 | rb_str_concat(s,comma); 221 | } 222 | // rb_funcall(s,concat,1,rb_str_new2("]")); 223 | rb_str_concat(s,rb_str_new2("]")); 224 | 225 | return rb_str_to_str(s); 226 | } 227 | 228 | static VALUE deque_first (VALUE self) 229 | { 230 | deque* dq = NULL; 231 | Data_Get_Struct(self, deque, dq); 232 | if (!dq) 233 | rb_raise (rb_eException, "No Deque Object"); 234 | 235 | return (*dq).front(); 236 | } 237 | 238 | static VALUE deque_last (VALUE self) 239 | { 240 | deque* dq = NULL; 241 | Data_Get_Struct(self, deque, dq); 242 | if (!dq) 243 | rb_raise (rb_eException, "No Deque Object"); 244 | 245 | return (*dq).back(); 246 | } 247 | 248 | static VALUE deque_replace (VALUE self, VALUE new_dq) 249 | { 250 | deque* dq = NULL; 251 | deque* ndq = NULL; 252 | deque::iterator q_iterator; 253 | Data_Get_Struct(self, deque, dq); 254 | Data_Get_Struct(new_dq, deque, ndq); 255 | if (!dq) 256 | rb_raise (rb_eException, "No Deque Object"); 257 | 258 | if (!ndq) 259 | rb_raise (rb_eException, "No Deque object to copy"); 260 | 261 | (*dq).clear(); 262 | for ( q_iterator = (*ndq).begin(); q_iterator != (*ndq).end(); q_iterator++ ) { 263 | (*dq).push_back(*q_iterator); 264 | } 265 | 266 | return self; 267 | } 268 | 269 | static VALUE deque_at (VALUE self, VALUE pos) 270 | { 271 | deque* dq = NULL; 272 | long c_pos = rb_num2long(pos); 273 | 274 | Data_Get_Struct(self, deque, dq); 275 | 276 | if (!dq) 277 | rb_raise (rb_eException, "No Deque Object"); 278 | 279 | if ((c_pos < 0) || (c_pos >= (*dq).size())) 280 | rb_raise (rb_eException, "Out of bounds index on Deque object"); 281 | 282 | return *((*dq).begin() + c_pos); 283 | } 284 | 285 | static VALUE deque_delete (VALUE self, VALUE obj) 286 | { 287 | deque* dq = NULL; 288 | deque::iterator q_iterator; 289 | deque::iterator last_q_iterator; 290 | 291 | Data_Get_Struct(self, deque, dq); 292 | 293 | if (!dq) 294 | rb_raise (rb_eException, "No Deque Object"); 295 | 296 | last_q_iterator = (*dq).end(); 297 | for ( q_iterator = (*dq).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 298 | if (rb_equal(*q_iterator, obj)) { 299 | (*dq).erase(q_iterator); 300 | break; 301 | } 302 | } 303 | 304 | return self; 305 | } 306 | 307 | static VALUE deque_delete_at (VALUE self, VALUE pos) 308 | { 309 | deque* dq = NULL; 310 | deque::iterator q_iterator; 311 | long c_pos = rb_num2long(pos); 312 | 313 | Data_Get_Struct(self, deque, dq); 314 | 315 | if (!dq) 316 | rb_raise (rb_eException, "No Deque Object"); 317 | 318 | if ((c_pos < 0) || (c_pos >= (*dq).size())) 319 | rb_raise (rb_eException, "Out of bounds index on Deque object"); 320 | 321 | q_iterator = (*dq).begin(); 322 | for ( int n = 0; n < c_pos; n++) { 323 | q_iterator++; 324 | } 325 | (*dq).erase(q_iterator); 326 | 327 | return *q_iterator; 328 | } 329 | 330 | static VALUE deque_insert (VALUE self, VALUE pos, VALUE val) 331 | { 332 | deque* dq = NULL; 333 | deque::iterator q_iterator; 334 | long c_pos = rb_num2long(pos); 335 | 336 | Data_Get_Struct(self, deque, dq); 337 | 338 | if (!dq) 339 | rb_raise (rb_eException, "No Deque Object"); 340 | 341 | if ((c_pos < 0) || (c_pos > (*dq).size())) 342 | rb_raise (rb_eException, "Out of bounds index on Deque object"); 343 | 344 | q_iterator = (*dq).begin() + c_pos; 345 | (*dq).insert(q_iterator,val); 346 | 347 | return self; 348 | } 349 | 350 | static VALUE deque_assign_at (VALUE self, VALUE pos, VALUE val) 351 | { 352 | deque_delete(self,pos); 353 | deque_insert(self,pos,val); 354 | 355 | return self; 356 | } 357 | 358 | static VALUE deque_each (VALUE self) 359 | { 360 | deque* dq = NULL; 361 | deque::iterator q_iterator; 362 | deque::iterator last_q_iterator; 363 | 364 | Data_Get_Struct(self, deque, dq); 365 | 366 | if (!dq) 367 | rb_raise (rb_eException, "No Deque Object"); 368 | 369 | last_q_iterator = (*dq).end(); 370 | for ( q_iterator = (*dq).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 371 | rb_yield(*q_iterator); 372 | } 373 | 374 | return self; 375 | } 376 | 377 | static VALUE deque_index (VALUE self, VALUE match_obj) 378 | { 379 | deque* dq = NULL; 380 | deque::iterator q_iterator; 381 | deque::iterator last_q_iterator; 382 | 383 | Data_Get_Struct(self, deque, dq); 384 | if (!dq) 385 | rb_raise (rb_eException, "No Deque Object"); 386 | 387 | last_q_iterator = (*dq).end(); 388 | int pos = 0; 389 | for ( q_iterator = (*dq).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 390 | if (rb_equal(*q_iterator,match_obj) == Qtrue) { 391 | return INT2NUM(pos); 392 | } else { 393 | pos++; 394 | } 395 | } 396 | return Qnil; 397 | } 398 | 399 | /********************** 400 | Init_deque 401 | **********************/ 402 | 403 | extern "C" void Init_deque() 404 | { 405 | SwiftcoreModule = rb_define_module ("Swiftcore"); 406 | DequeClass = rb_define_class_under (SwiftcoreModule, "Deque", rb_cObject); 407 | 408 | rb_define_module_function (DequeClass, "new", (VALUE(*)(...))deque_new, 0); 409 | rb_define_method (DequeClass, "unshift", (VALUE(*)(...))deque_push_front,1); 410 | rb_define_method (DequeClass, "shift", (VALUE(*)(...))deque_pop_front,0); 411 | rb_define_method (DequeClass, "push", (VALUE(*)(...))deque_push_back,1); 412 | rb_define_method (DequeClass, "<<", (VALUE(*)(...))deque_push_back,1); 413 | rb_define_method (DequeClass, "pop", (VALUE(*)(...))deque_pop_back,0); 414 | rb_define_method (DequeClass, "size", (VALUE(*)(...))deque_size,0); 415 | rb_define_method (DequeClass, "length", (VALUE(*)(...))deque_size,0); 416 | rb_define_method (DequeClass, "max_size", (VALUE(*)(...))deque_max_size,0); 417 | rb_define_method (DequeClass, "clear", (VALUE(*)(...))deque_clear,0); 418 | rb_define_method (DequeClass, "empty?", (VALUE(*)(...))deque_empty,0); 419 | rb_define_method (DequeClass, "to_s", (VALUE(*)(...))deque_to_s,0); 420 | rb_define_method (DequeClass, "to_a", (VALUE(*)(...))deque_to_a,0); 421 | rb_define_method (DequeClass, "first", (VALUE(*)(...))deque_first,0); 422 | rb_define_method (DequeClass, "last", (VALUE(*)(...))deque_last,0); 423 | rb_define_method (DequeClass, "replace", (VALUE(*)(...))deque_replace,1); 424 | rb_define_method (DequeClass, "inspect", (VALUE(*)(...))deque_inspect,0); 425 | rb_define_method (DequeClass, "at", (VALUE(*)(...))deque_at,1); 426 | rb_define_method (DequeClass, "[]", (VALUE(*)(...))deque_at,1); 427 | rb_define_method (DequeClass, "delete", (VALUE(*)(...))deque_delete,1); 428 | rb_define_method (DequeClass, "delete_at", (VALUE(*)(...))deque_delete_at,1); 429 | rb_define_method (DequeClass, "insert", (VALUE(*)(...))deque_insert,2); 430 | rb_define_method (DequeClass, "[]=", (VALUE(*)(...))deque_assign_at,2); 431 | rb_define_method (DequeClass, "each", (VALUE(*)(...))deque_each,0); 432 | rb_define_method (DequeClass, "index", (VALUE(*)(...))deque_index,1); 433 | 434 | rb_include_module (DequeClass, rb_mEnumerable); 435 | } 436 | -------------------------------------------------------------------------------- /ext/map/rubymain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "string.h" 5 | using namespace std; 6 | 7 | #include 8 | 9 | static VALUE SwiftcoreModule; 10 | static VALUE MapClass; 11 | 12 | struct classcomp { 13 | bool operator() (const char *s1, const char *s2) const 14 | { 15 | if ( strcmp(s1,s2) < 0) { 16 | return true; 17 | } else { 18 | return false; 19 | } 20 | } 21 | }; 22 | 23 | typedef std::map rb_map; 24 | typedef std::map::iterator rb_map_iterator; 25 | 26 | static void sptm_mark (rb_map* st) 27 | { 28 | rb_map_iterator q_iterator; 29 | if (st) { 30 | for ( q_iterator = (*st).begin(); q_iterator != (*st).end(); q_iterator++ ) { 31 | rb_gc_mark(q_iterator->second); 32 | } 33 | } 34 | } 35 | 36 | static void sptm_free (rb_map* st) 37 | { 38 | if (st) 39 | delete st; 40 | } 41 | 42 | static VALUE sptm_new (VALUE self) 43 | { 44 | rb_map* st = new rb_map; 45 | VALUE v = Data_Wrap_Struct (MapClass, sptm_mark, sptm_free, st); 46 | return v; 47 | } 48 | 49 | /* 50 | static VALUE sptm_parent (VALUE self) 51 | { 52 | rb_map* st = NULL; 53 | Data_Get_Struct(self, rb_map, st); 54 | if (!st) 55 | rb_raise (rb_eException, "No Map Object"); 56 | 57 | if ((*st).empty()) { 58 | return Qnil; 59 | } else { 60 | rb_map_iterator parent = (*st).parent(); 61 | VALUE r = rb_ary_new(); 62 | 63 | rb_ary_push(r,rb_str_new2(parent->first)); 64 | rb_ary_push(r,parent->second); 65 | return r; 66 | } 67 | } 68 | */ 69 | 70 | static VALUE sptm_pop_front (VALUE self) 71 | { 72 | rb_map* st = NULL; 73 | Data_Get_Struct(self, rb_map, st); 74 | if (!st) 75 | rb_raise (rb_eException, "No Map Object"); 76 | 77 | if ((*st).empty()) { 78 | return Qnil; 79 | } else { 80 | rb_map_iterator front = (*st).begin(); 81 | VALUE r = rb_ary_new(); 82 | 83 | rb_ary_push(r,rb_str_new2(front->first)); 84 | rb_ary_push(r,front->second); 85 | (*st).erase(front); 86 | return r; 87 | } 88 | } 89 | 90 | static VALUE sptm_pop_back (VALUE self) 91 | { 92 | rb_map* st = NULL; 93 | Data_Get_Struct(self, rb_map, st); 94 | if (!st) 95 | rb_raise (rb_eException, "No Map Object"); 96 | 97 | if ((*st).empty()) { 98 | return Qnil; 99 | } else { 100 | rb_map_iterator back = (*st).end(); 101 | back--; 102 | VALUE r = rb_ary_new(); 103 | rb_ary_push(r,rb_str_new2(back->first)); 104 | rb_ary_push(r,back->second); 105 | (*st).erase(back); 106 | return r; 107 | } 108 | } 109 | 110 | 111 | static VALUE sptm_size (VALUE self) 112 | { 113 | rb_map* st = NULL; 114 | Data_Get_Struct(self, rb_map, st); 115 | if (!st) 116 | rb_raise (rb_eException, "No Map Object"); 117 | 118 | return INT2NUM((*st).size()); 119 | } 120 | 121 | 122 | static VALUE sptm_max_size (VALUE self) 123 | { 124 | rb_map* st = NULL; 125 | Data_Get_Struct(self, rb_map, st); 126 | if (!st) 127 | rb_raise (rb_eException, "No Map Object"); 128 | 129 | return INT2NUM((*st).max_size()); 130 | } 131 | 132 | static VALUE sptm_clear (VALUE self) 133 | { 134 | rb_map* st = NULL; 135 | Data_Get_Struct(self, rb_map, st); 136 | if (!st) 137 | rb_raise (rb_eException, "No Map Object"); 138 | 139 | (*st).clear(); 140 | return self; 141 | } 142 | 143 | static VALUE sptm_empty (VALUE self) 144 | { 145 | rb_map* st = NULL; 146 | Data_Get_Struct(self, rb_map, st); 147 | if (!st) 148 | rb_raise (rb_eException, "No Map Object"); 149 | 150 | if ((*st).empty()) { 151 | return Qtrue; 152 | } else { 153 | return Qfalse; 154 | } 155 | } 156 | 157 | static VALUE sptm_to_s (VALUE self) 158 | { 159 | VALUE s = rb_str_new2(""); 160 | 161 | rb_map* st = NULL; 162 | rb_map_iterator q_iterator; 163 | Data_Get_Struct(self, rb_map, st); 164 | if (!st) 165 | rb_raise (rb_eException, "No Map Object"); 166 | 167 | for ( q_iterator = (*st).begin(); q_iterator != (*st).end(); q_iterator++ ) { 168 | rb_str_concat(s,rb_str_new2(q_iterator->first)); 169 | rb_str_concat(s,rb_convert_type(q_iterator->second, T_STRING, "String", "to_str")); 170 | } 171 | 172 | return rb_str_to_str(s); 173 | } 174 | 175 | static VALUE sptm_to_a (VALUE self) 176 | { 177 | VALUE ary = rb_ary_new(); 178 | 179 | rb_map* st = NULL; 180 | rb_map_iterator q_iterator; 181 | Data_Get_Struct(self, rb_map, st); 182 | if (!st) 183 | rb_raise (rb_eException, "No Map Object"); 184 | 185 | for ( q_iterator = (*st).begin(); q_iterator != (*st).end(); q_iterator++ ) { 186 | rb_ary_push(ary,rb_str_new2(q_iterator->first)); 187 | rb_ary_push(ary,q_iterator->second); 188 | } 189 | 190 | return ary; 191 | } 192 | 193 | static VALUE sptm_inspect (VALUE self) 194 | { 195 | VALUE s = rb_str_new2("{"); 196 | VALUE arrow = rb_str_new2("=>"); 197 | VALUE comma = rb_str_new2(","); 198 | 199 | cout << "*1\n"; 200 | 201 | rb_map* st = NULL; 202 | rb_map_iterator q_iterator; 203 | rb_map_iterator last_q_iterator; 204 | rb_map_iterator before_last_q_iterator; 205 | Data_Get_Struct(self, rb_map, st); 206 | if (!st) 207 | rb_raise (rb_eException, "No Map Object"); 208 | 209 | cout << "*1\n"; 210 | if (!((*st).end() == (*st).begin())) { 211 | before_last_q_iterator = last_q_iterator = (*st).end(); 212 | before_last_q_iterator--; 213 | 214 | cout << "*1\n"; 215 | for ( q_iterator = (*st).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 216 | cout << "*1\n"; 217 | rb_str_concat(s,rb_inspect(rb_str_new2((*q_iterator).first))); 218 | rb_str_concat(s,arrow); 219 | rb_str_concat(s,rb_inspect((*q_iterator).second)); 220 | if (q_iterator != before_last_q_iterator) 221 | rb_str_concat(s,comma); 222 | } 223 | } 224 | 225 | rb_str_concat(s,rb_str_new2("}")); 226 | 227 | return rb_str_to_str(s); 228 | } 229 | 230 | static VALUE sptm_first (VALUE self) 231 | { 232 | rb_map* st = NULL; 233 | Data_Get_Struct(self, rb_map, st); 234 | if (!st) 235 | rb_raise (rb_eException, "No Map Object"); 236 | 237 | if ((*st).empty()) { 238 | return Qnil; 239 | } else { 240 | rb_map_iterator front = (*st).begin(); 241 | VALUE r = rb_ary_new(); 242 | rb_ary_push(r,rb_str_new2(front->first)); 243 | rb_ary_push(r,front->second); 244 | return r; 245 | } 246 | } 247 | 248 | static VALUE sptm_last (VALUE self) 249 | { 250 | rb_map* st = NULL; 251 | Data_Get_Struct(self, rb_map, st); 252 | if (!st) 253 | rb_raise (rb_eException, "No Map Object"); 254 | 255 | if ((*st).empty()) { 256 | return Qnil; 257 | } else { 258 | rb_map_iterator back = (*st).end(); 259 | back--; 260 | VALUE r = rb_ary_new(); 261 | rb_ary_push(r,rb_str_new2(back->first)); 262 | rb_ary_push(r,back->second); 263 | return r; 264 | } 265 | } 266 | 267 | /* 268 | static VALUE sptm_replace (VALUE self, VALUE new_st) 269 | { 270 | rb_map* st = NULL; 271 | rb_map* nst = NULL; 272 | rb_map_iterator q_iterator; 273 | Data_Get_Struct(self, rb_map, st); 274 | Data_Get_Struct(new_st, rb_map, nst); 275 | if (!st) 276 | rb_raise (rb_eException, "No Map Object"); 277 | 278 | if (!nst) 279 | rb_raise (rb_eException, "No Map object to copy"); 280 | 281 | (*st).clear(); 282 | for ( q_iterator = (*nst).begin(); q_iterator != (*nst).end(); q_iterator++ ) { 283 | (*st).push_back(*q_iterator); 284 | } 285 | 286 | return self; 287 | } 288 | */ 289 | 290 | 291 | static VALUE sptm_insert (VALUE self, VALUE key, VALUE val) 292 | { 293 | rb_map* st = NULL; 294 | std::pair rv; 295 | std::pair sp; 296 | char * skey; 297 | char * svp; 298 | 299 | svp = StringValuePtr(key); 300 | skey = new char[strlen(svp)+1]; 301 | strcpy(skey,svp); 302 | 303 | Data_Get_Struct(self, rb_map, st); 304 | 305 | if (!st) 306 | rb_raise (rb_eException, "No Map Object"); 307 | 308 | sp = make_pair(skey,val); 309 | rv = (*st).insert(sp); 310 | if (!rv.second) { 311 | (*st).erase(rv.first); 312 | (*st).insert(sp); 313 | } 314 | 315 | return self; 316 | } 317 | 318 | static VALUE sptm_at (VALUE self, VALUE key) 319 | { 320 | rb_map* st = NULL; 321 | rb_map_iterator q_iterator; 322 | 323 | char * skey = StringValuePtr(key); 324 | 325 | Data_Get_Struct(self, rb_map, st); 326 | 327 | if (!st) 328 | rb_raise (rb_eException, "No Map Object"); 329 | 330 | q_iterator = (*st).find(skey); 331 | if (q_iterator == (*st).end()) { 332 | return Qnil; 333 | } else { 334 | return q_iterator->second; 335 | } 336 | } 337 | 338 | 339 | static VALUE sptm_delete (VALUE self, VALUE key) 340 | { 341 | rb_map* st = NULL; 342 | rb_map_iterator q_iterator; 343 | 344 | char * skey = StringValuePtr(key); 345 | 346 | Data_Get_Struct(self, rb_map, st); 347 | 348 | if (!st) 349 | rb_raise (rb_eException, "No Map Object"); 350 | 351 | q_iterator = (*st).find(skey); 352 | if (q_iterator == (*st).end()) { 353 | return Qnil; 354 | } else { 355 | (*st).erase(q_iterator); 356 | } 357 | 358 | return self; 359 | } 360 | 361 | /* 362 | static VALUE sptm_delete_value (VALUE self, VALUE obj) 363 | { 364 | rb_map* st = NULL; 365 | rb_map_iterator q_iterator; 366 | rb_map_iterator last_q_iterator; 367 | 368 | Data_Get_Struct(self, rb_map, st); 369 | 370 | if (!st) 371 | rb_raise (rb_eException, "No Map Object"); 372 | 373 | last_q_iterator = (*st).end(); 374 | for ( q_iterator = (*st).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 375 | if (rb_equal(q_iterator->second, obj)) { 376 | (*st).erase(q_iterator); 377 | break; 378 | } 379 | } 380 | 381 | return self; 382 | } 383 | */ 384 | 385 | static VALUE sptm_each (VALUE self) 386 | { 387 | rb_map* st = NULL; 388 | rb_map_iterator q_iterator; 389 | rb_map_iterator last_q_iterator; 390 | 391 | Data_Get_Struct(self, rb_map, st); 392 | 393 | if (!st) 394 | rb_raise (rb_eException, "No Map Object"); 395 | 396 | last_q_iterator = (*st).end(); 397 | for ( q_iterator = (*st).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 398 | rb_yield_values(2,rb_str_new2(q_iterator->first),q_iterator->second); 399 | } 400 | 401 | return self; 402 | } 403 | 404 | static VALUE sptm_index (VALUE self, VALUE match_obj) 405 | { 406 | rb_map* st = NULL; 407 | rb_map_iterator q_iterator; 408 | rb_map_iterator last_q_iterator; 409 | 410 | Data_Get_Struct(self, rb_map, st); 411 | 412 | if (!st) 413 | rb_raise (rb_eException, "No Map Object"); 414 | 415 | last_q_iterator = (*st).end(); 416 | for ( q_iterator = (*st).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 417 | if (rb_equal(q_iterator->second, match_obj)) { 418 | return rb_str_new2(q_iterator->first); 419 | } 420 | } 421 | return Qnil; 422 | } 423 | 424 | static VALUE sptm_keys (VALUE self) 425 | { 426 | rb_map* st = NULL; 427 | rb_map_iterator q_iterator; 428 | rb_map_iterator last_q_iterator; 429 | 430 | Data_Get_Struct(self, rb_map, st); 431 | 432 | if (!st) 433 | rb_raise (rb_eException, "No Map Object"); 434 | 435 | last_q_iterator = (*st).end(); 436 | VALUE r = rb_ary_new(); 437 | for ( q_iterator = (*st).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 438 | rb_ary_push(r,rb_str_new2(q_iterator->first)); 439 | } 440 | 441 | return r; 442 | } 443 | 444 | static VALUE sptm_values (VALUE self) 445 | { 446 | rb_map* st = NULL; 447 | rb_map_iterator q_iterator; 448 | rb_map_iterator last_q_iterator; 449 | 450 | Data_Get_Struct(self, rb_map, st); 451 | 452 | if (!st) 453 | rb_raise (rb_eException, "No Map Object"); 454 | 455 | last_q_iterator = (*st).end(); 456 | VALUE r = rb_ary_new(); 457 | for ( q_iterator = (*st).begin(); q_iterator != last_q_iterator; q_iterator++ ) { 458 | rb_ary_push(r,q_iterator->second); 459 | } 460 | 461 | return r; 462 | } 463 | 464 | /********************** 465 | Init_sptm 466 | **********************/ 467 | 468 | extern "C" void Init_map() 469 | { 470 | SwiftcoreModule = rb_define_module ("Swiftcore"); 471 | MapClass = rb_define_class_under (SwiftcoreModule, "Map", rb_cObject); 472 | 473 | rb_define_module_function (MapClass, "new", (VALUE(*)(...))sptm_new, 0); 474 | rb_define_method (MapClass, "shift", (VALUE(*)(...))sptm_pop_front,0); 475 | rb_define_method (MapClass, "pop", (VALUE(*)(...))sptm_pop_back,0); 476 | rb_define_method (MapClass, "size", (VALUE(*)(...))sptm_size,0); 477 | rb_define_method (MapClass, "length", (VALUE(*)(...))sptm_size,0); 478 | rb_define_method (MapClass, "max_size", (VALUE(*)(...))sptm_max_size,0); 479 | rb_define_method (MapClass, "clear", (VALUE(*)(...))sptm_clear,0); 480 | rb_define_method (MapClass, "empty?", (VALUE(*)(...))sptm_empty,0); 481 | rb_define_method (MapClass, "to_s", (VALUE(*)(...))sptm_to_s,0); 482 | rb_define_method (MapClass, "to_a", (VALUE(*)(...))sptm_to_a,0); 483 | rb_define_method (MapClass, "first", (VALUE(*)(...))sptm_first,0); 484 | rb_define_method (MapClass, "last", (VALUE(*)(...))sptm_last,0); 485 | //rb_define_method (MapClass, "parent", (VALUE(*)(...))sptm_parent,0); 486 | //rb_define_method (MapClass, "replace", (VALUE(*)(...))sptm_replace,1); 487 | rb_define_method (MapClass, "inspect", (VALUE(*)(...))sptm_inspect,0); 488 | rb_define_method (MapClass, "at", (VALUE(*)(...))sptm_at,1); 489 | rb_define_method (MapClass, "[]", (VALUE(*)(...))sptm_at,1); 490 | rb_define_method (MapClass, "delete", (VALUE(*)(...))sptm_delete,1); 491 | rb_define_method (MapClass, "insert", (VALUE(*)(...))sptm_insert,2); 492 | rb_define_method (MapClass, "[]=", (VALUE(*)(...))sptm_insert,2); 493 | rb_define_method (MapClass, "each", (VALUE(*)(...))sptm_each,0); 494 | rb_define_method (MapClass, "index", (VALUE(*)(...))sptm_index,1); 495 | rb_define_method (MapClass, "keys", (VALUE(*)(...))sptm_keys,0); 496 | rb_define_method (MapClass, "values", (VALUE(*)(...))sptm_values,0); 497 | 498 | /* == [] [] []= clear default default= default_proc delete delete_if each each_key each_pair each_value empty? fetch has_key? has_value? include? index indexes indices initialize_copy inspect invert key? keys length member? merge merge! new pretty_print pretty_print_cycle rehash reject reject! replace select shift size sort store to_a to_hash to_s to_yaml update value? values values_at */ 499 | // rb_include_module (MapClass, rb_mEnumerable); 500 | } 501 | --------------------------------------------------------------------------------