[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: вот блин, залил исходники подсвеченные со стилями, а ЖЖшка стили и подсветку убрала. причём при редактировании заметки всё стильное и цветное. как его правильно в ЖаЖу заливать ?)
This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

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. 12th, 2026 11:25 pm
Powered by Dreamwidth Studios