#!/usr/bin/env ruby1.8 # # vim: set sw=2 ts=2 expandtab: # # SMS Web Service client example library # # Copyright (C) 2010 by James Maki # # Author: James Maki # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # require 'rubygems' require 'net/http' require 'cgi' require 'inform' require 'smswsc/utils' require 'smswsc/phonebook_entry' require 'smswsc/sms_message' module SMSWSC module Error class General < RuntimeError; end class HTTP < General; end class XML < General; end class BadRequest < General; end class Unauthorized < General; end class Forbidden < General; end class NotFound < General; end class ServiceUnavailable < General; end end SMSWS_PATH = "/smsws/v1" PHONEBOOK_PATH = Utils.ref(SMSWS_PATH, "phonebook") MESSAGES_PATH = Utils.ref(SMSWS_PATH, "messages") SEND_PATH = Utils.ref(SMSWS_PATH, "send") class Client include Utils DEFAULT_PORT = 5857 VERSION = '0.1' attr_accessor :use_ssl attr_accessor :http_debug attr_accessor :http_read_timeout def initialize(username, passwd, host, port = DEFAULT_PORT) @host = host @port = port @username = username @passwd = passwd @error = "" @headers = { 'Content-Type' => 'application/xml', 'User-Agent' => "smswsc/#{VERSION}", } @use_ssl = false @http_debug = false @http_read_timeout = 300 end def user_agent(ua) @headers['User-Agent'] = ua end def phonebook_query(path = PHONEBOOK_PATH) case path when PhonebookEntry path = path.uri.to_s when String when URI path = path.to_s else raise ArgumentError, "Invalid param type: #{path.class}" end phonebook = [] response = get_resource(path) root = process_response(response, Net::HTTPOK) root.elements.each("phonebook-entry") do |entry| phonebook << PhonebookEntry.load_xml(entry) end return phonebook end def sms_messages_query(path = MESSAGES_PATH) case path when SMSMessage path = path.uri.to_s when String when URI path = path.to_s else raise ArgumentError, "Invalid param type: #{path.class}" end msgs = [] response = get_resource(path) root = process_response(response, Net::HTTPOK) root.elements.each("sms-message") do |entry| msgs << SMSMessage.load_xml(entry) end return msgs end def sms_messages_delete(path = MESSAGES_PATH) case path when SMSMessage path = path.uri.to_s when String when URI path = path.to_s else raise ArgumentError, "Invalid param type: #{path.class}" end response = delete_resource(path) root = process_response(response, Net::HTTPOK) return true end def sms_send(msg) response = post_resource(SEND_PATH, msg.to_xml) root = process_response(response, Net::HTTPCreated) return true end private def process_response(response, klass) root = response_xml(response) if root message = root.elements['status-message'].text else message = "response message not given" end unless response.instance_of?(klass) if response.instance_of?(Net::HTTPBadRequest) raise Error::BadRequest, "Bad Request: " + message elsif response.instance_of?(Net::HTTPUnauthorized) raise Error::Unauthorized, "Unauthorized: " + message elsif response.instance_of?(Net::HTTPForbidden) raise Error::Forbidden, "Forbidden: " + message elsif response.instance_of?(Net::HTTPNotFound) raise Error::NotFound, "Not Found: " + message elsif response.instance_of?(Net::HTTPServiceUnavailable) raise Error::ServiceUnavailable, "Service Unavailable: " + message else raise Error::General, "Failed to fulfill request: " + message end end return root end def response_xml(response) return nil unless response.content_type.to_s.downcase == 'application/xml' begin document = REXML::Document.new(response.body) root = document.root rescue REXML::ParseException => e Inform.err(e.to_s) raise Error::XML, "failed to parse response XML" end return root end def authorize(request) if @username && @passwd request.basic_auth(@username, @passwd) end end def parse_qpath(url) url = URI.parse(url) qpath = url.path if url.query qpath += "?" + url.query end return qpath end def net_http http = Net::HTTP.new(@host, @port) http.read_timeout = @http_read_timeout http.set_debug_output($stderr) if @http_debug if @use_ssl http.use_ssl = @use_ssl http.verify_mode = OpenSSL::SSL::VERIFY_NONE end return http end def post_resource(ref, body, headers = {}) headers = @headers.merge(headers) qpath = parse_qpath(ref) begin request = Net::HTTP::Post.new(qpath, headers) authorize(request) response = net_http.start do |http| http.request(request, body) end rescue => e Inform.err("POST #{ref} failed: #{e.class}: #{e}") raise Error::HTTP, "POST #{ref} failed: #{e}" end return response end def delete_resource(ref, headers = {}) headers = @headers.merge(headers) qpath = parse_qpath(ref) begin request = Net::HTTP::Delete.new(qpath, headers) authorize(request) response = net_http.start do |http| http.request(request) end rescue => e Inform.err("DELETE #{ref} failed: #{e.class}: #{e}") raise Error::HTTP, "DELETE #{ref} failed: #{e}" end return response end def get_resource(ref, headers = {}) headers = @headers.merge(headers) qpath = parse_qpath(ref) begin request = Net::HTTP::Get.new(qpath, headers) authorize(request) response = net_http.start do |http| http.request(request) end rescue => e Inform.err("GET #{ref} failed: #{e.class}: #{e}") raise Error::HTTP, "GET #{ref} failed: #{e}" end return response end def put_resource(ref, body, headers = {}) headers = @headers.merge(headers) qpath = parse_qpath(ref) begin request = Net::HTTP::Put.new(qpath, headers) authorize(request) response = net_http.start do |http| http.request(request, body) end rescue => e Inform.err("PUT #{ref} failed: #{e.class}: #{e}") raise Error::HTTP, "PUT #{ref} failed: #{e}" end return response end end end if $0 == __FILE__ then SMSWS_ROOT = File.expand_path(File.dirname(__FILE__)) Dir.chdir(SMSWS_ROOT) SMSWS_CONFIG = "#{SMSWS_ROOT}/sms.config" $config = YAML::load_file(SMSWS_CONFIG) ENV['SMS_CONFIG'] = "#{SMSWS_CONFIG}" Inform.syslog = false if Inform.syslog Syslog.open("smswsc", Syslog::LOG_NDELAY, Syslog::LOG_LOCAL7) else Inform.level = Inform::LOG_ERR #Inform.level = Inform::LOG_DEBUG Inform.file = $stdout end client = SMSWSC::Client.new( $config["smsws"]["user"], $config["smsws"]["passwd"], "192.168.2.1", $config["smsws"]["port"] ) msgs = client.sms_messages_query p msgs pb = client.phonebook_query p pb msg = SMSWSC::SMSMessage.new msg.addr = "jcm" msg.user_data = "test me web by name" client.sms_send(msg) end