TFCweb  1.0.4 $Rev: 483 $
TFC Primavera 2012: Nucli d'un servidor web
CosResposta.cc
Veure la documentació d'aquest fitxer.
1 
8 /*
9  * Copyright (c) 2012 Toni Corvera
10  *
11  * This file is part of TFCWeb.
12  *
13  * TFCWeb is free software: you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation, either version 3 of the License, or
16  * (at your option) any later version.
17  *
18  * TFCWeb is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with TFCWeb. If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #include "CosResposta.h"
28 
29 #include <sstream>
30 #include <boost/lexical_cast.hpp>
31 #include <boost/regex.hpp>
32 
33 using namespace std;
34 
35 using boost::lexical_cast;
36 
37 namespace tfc {
38 
39 size_t CosResposta::envia(socket_tcp & socket, const string & buffer) const
41 {
42  string dades;
43  transformacio_ptr pTrans( transformacio() );
44  if (0 == pTrans.get()) {
45  // No cal transformar
46  dades = buffer;
47  }
48  else {
49  dades = pTrans->transforma( buffer );
50  }
51  return envia_literal(socket, dades); // throws ErrorEscrivintEnSocket
52 }
53 
54 size_t CosResposta::envia_literal(socket_tcp & socket, const string & buffer) const
56 {
57  // Quan el client tanca la sessió (p.e. CTRL+C mentre descarrega), es produeix
58  // un error "connection reset by peer"
59  try {
60  // socket.write_some envia el que pot (bloca fins escriure alguna cosa o fallar)
61  // asio::write() envia tot el buffer, blocant el que calgui
62  return boost::asio::write(socket, boost::asio::buffer(buffer));
63  }
64  catch (const boost::system::system_error &) {
65  // TODO: Es pot distingir si és un "connection reset by peer"?
66  throw ErrorEscrivintEnSocket();
67  }
68 }
69 
70 void CosResposta::envia_eom(socket_tcp & socket) const throw (ErrorEscrivintEnSocket)
71 {
72  if (0 != trans_.get()) {
73  envia_literal(socket, trans_->finalitza()); // throws ErrorEscrivintEnSocket
74  }
75 }
76 
77 
78 CosResposta::tipus_capsaleres CosResposta::capsaleres() const {
80  if (!ce_.empty()) {
81  tc.push_back( make_pair("Content-Encoding", ce_) );
82  }
83  if (!te_.empty()) {
84  tc.push_back( make_pair("Transfer-Encoding", te_) );
85  }
86  return tc;
87 }
88 
89 void CosResposta::determina_codificacions() {
90  using namespace boost;
91 
92  // chunked HA de ser acceptat per els clients HTTP/1.1
93  chunked_ok_ = peticio().versio() >= HTTP_1_1;
94 
95  if (peticio().versio() < HTTP_1_1) {
96  // Els clients HTTP/1.0 tècnicament poden implementar codificacions
97  // (gzip i compress), però no (necessàriament) han de suportar chunked
98  // Podem optar per codificar, no indicar un Content-Length, i tancar la
99  // connexió quan després de cada fitxer servit, però per simplicitat
100  // en aquesta implementació símplement no es codificarà en versions
101  // d'HTTP menors d'1.1
102  deflate_ok_ = false;
103  chunked_ok_ = false;
104  return;
105  }
106  assert( peticio().versio() >= HTTP_1_1 );
107 
108  // Transformacions acceptades
109  const string tr = peticio().capsalera("Accept-Encoding");
110  if (tr.empty() || "identity" == to_lower_copy(tr)) {
111  // No té gaire sentit aplicar "identity"
112  deflate_ok_ = false;
113  chunked_ok_ = false;
114  return;
115  }
116 
117  char_separator<char> separador(",");
118  tokenizer< char_separator<char> > tokens(tr, separador);
119 
120  // Com que només s'implementen deflate i chunked no es
121  // generalitza més el codi
122 
123  const boost::regex re_espais("\\s*");
124  for (auto it=tokens.begin(); it != tokens.end(); ++it) {
125  // Elimina els espais...
126  const string enc = boost::regex_replace(*it, re_espais, "");
127  // ...i compara sense diferenciar majús./minus.
128  if ("deflate" == to_lower_copy(enc)) {
129  deflate_ok_ = true;
130  break;
131  }
132  }
133 
134  assert( chunked_ok_ );
135 
136  // Només s'implementen deflate i chunked
137  // deflate presenta el problema de calcular la mida, per simplicitat
138  // només s'aplica deflate quan es combina amb chunked
139  // chunked per sí sol només és rellevant quan no es pot especificar la mida
140  // XXX: implementar deflate si la mida es coneix
141  if (!deflate_ok_) {
142  te_ = "chunked";
143  // trans_.reset( new TransformacioChunked() );
144  }
145  else {
146  te_ = "chunked";
147  ce_ = "deflate";
149  trans_.reset( pTrans );
150  pTrans->afegeix<TransformacioDeflate>();
151  pTrans->afegeix<TransformacioChunked>();
152  }
153 }
154 
155 string CosPaginaError::html() const {
156  if (!cache_.empty()) {
157  return cache_;
158  }
159 
160  ostringstream ss;
161 
162  const string titol = lexical_cast<string>(static_cast<int>(error_)) + ": " +
163  FrasesEstatHTTP::frase(error_);
164 
165  ss << "<!DOCTYPE html>\n"
166  "<html>\n"
167  "<head>\n"
168  "\t<title>" << titol << "</title>\n"
169  "</head>\n"
170  "<body>\n"
171  "\t<h1>Error " << titol << "</h1>\n"
172  "\t<p>" << missatge_ << "</p>\n"
173  "\t<hr />\n"
174  "\t<address>" << Servidor::signatura() << "</address>\n"
175  "</body>\n"
176  "</html>\n";
177  cache_ = ss.str();
178 
179  return cache_;
180 }
181 
182 CosPaginaError::tipus_capsaleres CosPaginaError::capsaleres() const {
183  tipus_capsaleres c = RecursDinamic::capsaleres();
184  c.push_back( make_pair("Content-Type", "text/html; charset=utf-8") ); // FIXME: charset
185  c.push_back( make_pair("Content-Length", lexical_cast<string>(html().length())) );
186  return c;
187 }
188 
189 } // ns tfc
190 
191 // vim:set ts=4 et ai: //