[personal profile] muwlgr
Писал недавно web-приложение - этакий мостик или конвертор с web-запросов на внутреннюю учётную систему - и обратно. На своём любимом Ruby on Rails, есссно. Веб-запросы идут с платёжных терминалов и касаются финансовых операций, потому там применяется повсеместный SSL для защиты от их подслушивания и подделки. В первоначальной спецификации писалось, что web-сервер должен не только отдавать свой SSL-сертификат, но и запрашивать и проверять сертификат клиента. Прочитав маны Апача на mod_ssl, я решил, что мне нужна директива 'SSLVerifyClient required'. Но применить эту директиву в контексте единственной директории, в которой будет крутиться защищённое web-приложение, не удалось (apache 2.2.11, Ubuntu 9.04). Судя по логам отладки, в начале Апач устанавливает SSL-сессию только с серверным сертификатом, не требуя клиентского, а когда получает GET с URLом защищённого приложения, инициирует SSL-renegotiation и только тогда запрашивает сертификат клиента. На этом этапе сессия с большинством броузеров затыкается и через некоторое время отваливается по тайм-ауту. Я нашёл Апачевский баг 47055, немножко похожий на то, обо что я споткнулся. А также бумажку с ApacheCon-EU-2009 (по ключевым словам deciphering mod_ssl), в которой пишется, что в продвинутых вопросах применения SSL по-прежнему много кривизны то там, то сям (и не только в одном Apache). Да и как SSL может быть ровным, ведь он же designed by committee, притом комитет, должно быть, был такого высокого уровня, что всяким инженерам и технарям там уже просто не нашлось места. Не смог я сделать верификацию клиента и через stunnel -v2 -d. stunnel не делает renegotiation посреди сессии, зато у него какие-то странные понятия насчёт persistent SSL-сессий и их связи с HTTP-keepalive. В итоге всех эти мытарств решили слегка переделать спецификацию. От верификации клиента на уровне SSL отказаться, зато сделать цифровую подпись клиентских запросов и ответов web-приложения, для проверки подлинности того и другого (вдобавок к обмену ключами и шифрации трафика за счёт серверного SSL-сертификата). Для каждого web-запроса указали, значения каких параметров и в каком порядке склеить в одну строку через ';' для проверки их подписи (присылаемой отдельным параметром в 16-ричном виде), а для ответов в формате xml добавили параметр в рамку <response sign=...></response> , в котором должна храниться подпись его innerHTMLа, тоже в 16-ричном виде. У Ruby есть довольно красивая и вполне ООшная, хотя и не слишком подробно документированная, привязка к OpenSSL, и в Инете нашлось несколько образцов её применения, которые я творчески переработал для своих нужд. В результате появилась пара простых функций :
require 'openssl'
pubkey = OpenSSL::X509::Certificate.new(File.read('remote.cer')).public_key privkey = OpenSSL::PKey::RSA.new(File.read('server.key')) def sign_xml (s) # form a signed xml reply with signature in hexnums '<response sign="' + privkey.sign(OpenSSL::Digest::SHA1.new, s).unpack('H*')[0] + '">' + s + '</response>' end def verify_params (pnlist) # get values of named params in given order, join them by ';', get signature from hexnums and verify them all pubkey.verify(OpenSSL::Digest::SHA1.new, params[:sign].to_a.pack('H*'), pnlist.map{|i|params[i]}.join(';')) end
Поначалу было немножко боязно, т.к. система получалась совсем гетерогенной - пиплы с терминальной стороны писали вообще на .Netе и свой код мне не показывали. Но старые добрые pack/unpack не подвели и в этот раз. Наследие Перла, обкатанное долгими годами :> Не совсем понятно, почему unpack возвращает массив, а pack требует массива на вход, но пришлось написать [0] и .to_a, чтоб всех удовлетворить. Также, возможно, вместо pnlist.map{... params ...} можно было бы написать params.values_at(pnlist)

Внутренняя учётная система у конечных заказчиков, хммм... своеобразная. "Финансы без проблем" называется. С ней нужно общаться, кидая файлы запросов определённого формата с раширением .IN в сетевую шару и ожидая появления файлов .OUT . Притом русские буквы в ответах идут в кодировке cp866. И там ещё есть файлик UPTI со временной меткой, определяющий, была ли обновлена база со времени того или иного запроса, или ответ на него можно взять из кэша.
Ну и появились такие функции, соответственно :
require 'iconv'

def decodeFBP(fo) # split fields, convert text from cp866 to utf8, return them as list of lists 
 File.open(fo) do |f|
  return f.readlines.map { |z| z.split(/\xFA/).map { |s| Iconv.conv("UTF-8", "cp866", s) } }
 end
end

QN="/home/mwg/queue-Diler/" # queue/mailbox/exchange dir, usually mounted by smb from windows server
FU=QN+"UPTI" # file with last DB modification timestamp, used for refreshing saved replies

def queryFBP (fn, parmlist) # send ФБП query as a list of parameters in specified file name, wait for reply, decode and return it
 fo=QN+fn+".OUT"
 if test(?f, fo) # outfile exists
  return decodeFBP(fo) if test(?f, FU) && test(?<, FU, fo) # outfile does not need refresh
  File.delete(fo) # remove it before refreshing
 end
 fi=QN+fn+".IN"
 fi0 = fi + '0'
 File.open(fi0, 'w') {|f| f.write(parmlist.join("\xFA")+"\n") } # form a FBP query, probably should add encoding from utf8 to cp866 if Russian letters are used
 File.rename(fi0, fi) # commit - send it
 sleep 1 until test(?f, fo) # wait for answer to appear
 decodeFBP(fo)
end

Легаси и экзотика, конечно же, но я смог к ней приспособиться :>

И ещё вам отсыплю свою наработочку, которая у меня родилась, когда пришлось обрабатывать временнЫе интервалы. Типа, когда у такого-то работника запланированы рабочие часы, какие из этих часов уже заняты под какую-либо работу, а какие ещё можно занять.  Предполагается, что обрабатываемый объект представИм в виде массива или списка непустых и непересекающихся временнЫх интервалов, отсортированных по возрастанию. И я нарисовал функции, чтоб эти объекты теоретико-множественно объединять, пересекать и вычитать.
Даже нарисовал класс-обёртку для всего этого, с переопределёнными операциями. Правда, так им и не воспользовался. Там и класс-то не нужен. В функциях и так полный duck-typing.
Захотелось всё написать элегантненько-функциональненько-рекурсивненько и почти без сайд-эффектов, насколько это возможно в Ruby. Присваивания каждой переменной - только однократные, например.
И да, функции, которые _less - служебные. предназначены для операций с отсортированными аргументами и напрямую вызываться не должны.

#Let's represent finite nonempty interval sets as arrays of ranges.
#Let's assume that within each set, ranges do not intersect
#and are stored in ascending order
#Let's use half-open ranges, i.e. containing its begin but not its end,
#like in C++ STL.
#And let's don't store empty ranges like [a,a)
#We can have ranges of integers, floats, strings or times. 
#Each could have its own use.

#now some non-class functions :

def rs_union_less (a, b) # precondition : both a and b non-empty, 
 ha,*ta=a		 # a.first.begin < b.first.begin
 hb,*tb=b

 # filter empty heads
 return rs_union(ta,b) if ha.begin == ha.end
 return rs_union(a,tb) if hb.begin == hb.end

 # simple case : head of a does not intersect with head of b, 
 # so it goes into the result unchanged
 return [ha] + rs_union(ta,b) if ha.end < hb.begin

 # here, head of b is entirely contained in the head of a, so we just drop it
 return rs_union(a,tb) if ha.end >= hb.end

 return rs_union(ta,[ha.begin...hb.end]+tb) # take head of a, 
end					    # merge it to head of b and recurse

def rs_union(a,b) # set union or merge. what is in either of sets
 # simple cases
 return b if a.empty?
 return a if b.empty?

 return a.first.begin < b.first.begin ? rs_union_less(a,b) : rs_union_less(b,a)
end

def rs_merge_less(a,b)
 ha,*ta=a # a.first.begin < b.first.begin
 hb,*tb=b

 # filter empty heads
 return rs_merge(ta,b) if ha.begin == ha.end
 return rs_merge(a,tb) if hb.begin == hb.end

 return [ha] + rs_merge(ta,b)
end

def rs_merge(a,b) # like set union, but do not merge adjacent ranges
 # simple cases
 return b if a.empty?
 return a if b.empty?

 return a.first.begin < b.first.begin ? rs_merge_less(a,b) : rs_merge_less(b,a)
end

def rs_intersect_less(a,b) # precondition : both a and b non-empty, 
 ha,*ta=a		   # a.first.begin < b.first.begin
 hb,*tb=b

 # filter empty heads
 return rs_intersect(ta,b) if ha.begin == ha.end
 return rs_intersect(a,tb) if hb.begin == hb.end

 # simple case : head of a does not go into the result
 return rs_intersect(ta,b) if ha.end <= hb.begin

 # here, head of b is entirely contained in the head of a,
 return [hb] + rs_intersect(a,tb) if ha.end >= hb.end

 return [hb.begin...ha.end]+rs_intersect(ta,b) # take head of a, 
end					       # intersect it to head of b and recurse

def rs_intersect(a,b) # set intersection. what is in both sets
 # simple case
 return [] if a.empty? || b.empty?

 return a.first.begin < b.first.begin ? rs_intersect_less(a,b) : rs_intersect_less(b,a)
end

def rs_subtract(a,b) # set difference. what's in a but not in b
 return a if a.empty? || b.empty?

 ha,*ta=a
 hb,*tb=b
 # filter empty heads
 return rs_subtract(ta,b) if ha.begin == ha.end
 return rs_subtract(a,tb) if hb.begin == hb.end

 if ha.begin < hb.begin # a starts earlier than b
  return [ha]+rs_subtract(ta,b) if ha.end <= hb.begin
  # head of a does not intersect with head of b
  return [ha.begin...hb.begin]+rs_subtract(ta,b) if ha.end <= hb.end
  # shorten head of a at the end
  [ha.begin...hb.begin]+rs_subtract([hb.end...ha.end]+ta,tb)
  # take a middle out of head of a, and cancel head of b
 else # a starts later than b
  return rs_subtract(a,tb) if hb.end <= ha.begin # heads do not intersect
  return rs_subtract(ta,b) if ha.end <= hb.end # head of a is cancelled entirely
  rs_subtract([hb.end...ha.end]+ta,tb) # head of a is shortened by head of b
 end
end # such that rs_subtract([10...100],[50...60]) == [10...50,60...100]

def rs_xor(a,b) # that's trivial : what's in one of the sets but not in both
 return rs_subtract(rs_union(a,b),rs_intersect(a,b))
end


class RangeSet
 attr_reader :content

 def initialize(a)
  if a.is_a? Array
   @content = a
  else
   @content=[a] # for initializers like single range (a...b)
  end
 end

 def self.c(b) # pick @content from RangeSet, or use Array as is.
  return b.content if b.is_a? RangeSet
  return [b] if b.is_a? Range
  b
 end

 def -(b)
  return RangeSet.new(rs_subtract(@content, self.class.c(b)))
 end

 def +(b)
  return RangeSet.new(rs_union(@content, c(b)))
 end

 def *(b)
  return RangeSet.new(rs_intersect(@content, c(b)))
 end

 def ^(b)
  return RangeSet.new(rs_xor(@content, c(b)))
 end

end

Класс так и остался недорисованным, там ещё надо переопределить операторы [] и []= и перекидывать их на @content. Или все непонятые методы перекидывать на @content ?

В общем, так  и живу. Берусь только за те затеи, в которых могу выбрать приемлемый для себя инструмент. Или в которых премлемый инструмент уже выбрали за меня.
Кроме Ruby, нормально отношусь к Perlу с его CPANом и к /bin/sh (иногда могу написать нечто довольно сложное на шелле в качестве главного языка). В подобающих случаях допускаю применение Си.
Хотел бы наработать у себя побольше навыков применения Pythonа и его фреймворков, но пока не удавалось встрять в готовые проекты на них.
C++, C#, Javу - не люблю. Как и PHP и ASP[.Net]. По разным причинам, заслуживающим отдельного рассказа.
Трогаю их только по большой нужде. Когда другого пути нет. Когда на горизонте маячит угроза голода и бедности :>
Как в анекдоте про мужчин и коз на далёком острове :>

(upd: вот блин, залил исходники подсвеченные со стилями, а ЖЖшка стили и подсветку убрала. причём при редактировании заметки всё стильное и цветное. как его правильно в ЖаЖу заливать ?)

Date: 2009-08-13 09:12 am (UTC)
From: [identity profile] profuel.livejournal.com
по-моему — никак не подсветить, так как блок стайл выкусился в целях избежания дырок. вот и остались все классы, а их описания — стерлись. возможно, на paid-account можно и стили оставлять — не помню.

Date: 2009-08-13 01:00 pm (UTC)
From: [identity profile] muwlgr.livejournal.com
Дык я делюсь чем не жалко. Всё конфиденциальное остаётся необнародованным, как ему и положено.
А пишу, чтоб денег заработать, для чего ж ещё :>
Совершенствование скиллзов - это так, сайд-эффект :>

Profile

Volodymyr Mutel

February 2026

S M T W T F S
1234567
89 1011121314
15161718192021
22232425262728

Style Credit

Expand Cut Tags

No cut tags
Page generated Feb. 13th, 2026 07:58 am
Powered by Dreamwidth Studios