VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gvhttpserverpeer.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2017 Graeme Walker
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // gvhttpserverpeer.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gvhttpserverpeer.h"
23 #include "grjpeg.h"
24 #include "groot.h"
25 #include "ghexdump.h"
26 #include "grimagebuffer.h"
27 #include "grglyph.h"
28 #include "gstr.h"
29 #include "glog.h"
30 #include "gassert.h"
31 #include <algorithm>
32 
33 Gv::HttpServerPeer::HttpServerPeer( GNet::Server::PeerInfo peer_info , Sources & sources , const Resources & resources , const Config & config ) :
34  GNet::BufferedServerPeer(peer_info,"\r\n") ,
35  m_sources(sources) ,
36  m_source(*this) ,
37  m_resources(resources) ,
38  m_config_default(config) ,
39  m_config(config) ,
40  m_idle_timer(*this,&HttpServerPeer::onIdleTimeout,*this) ,
41  m_data_timer(*this,&HttpServerPeer::onDataTimeout,*this) ,
42  m_image_size(0U) ,
43  m_sending(0U) ,
44  m_image_number(1U) ,
45  m_state(s_init) ,
46  m_content_length(0U)
47 {
48  if( m_config.idleTimeout() != 0U )
49  m_idle_timer.startTimer( m_config.idleTimeout() ) ;
50 }
51 
53 {
54 }
55 
56 void Gv::HttpServerPeer::onSecure( const std::string & )
57 {
58 }
59 
60 void Gv::HttpServerPeer::selectSource( const std::string & url_path )
61 {
62  if( m_sources.valid(url_path) )
63  {
64  bool new_source = m_sources.select( url_path , m_source ) ;
65  if( new_source )
66  {
67  m_image_size = 0U ;
68  m_image_number = 1U ;
69  m_image.clear() ;
70  m_data_timer.cancelTimer() ;
71  G_DEBUG( "Gv::HttpServerPeer::selectSource: new source: source-name=[" << m_source.name() << "]" ) ;
72  }
73  }
74 }
75 
76 bool Gv::HttpServerPeer::onReceive( const std::string & line )
77 {
78  if( m_config.moreVerbose() )
79  G_LOG( "Gv::HttpServerPeer::onReceive: rx<<: \"" << G::Str::printable(line) << "\"" ) ;
80 
81  if( m_config.idleTimeout() != 0U )
82  m_idle_timer.startTimer( m_config.idleTimeout() ) ;
83 
84  if( ( m_state == s_init || m_state == s_idle ) && line.find("GET") == 0U )
85  {
86  G::StringArray part ;
87  G::Str::splitIntoTokens( line , part , " \t" ) ;
88  part.push_back( std::string() ) ;
89  if( m_config.moreVerbose() )
90  G_LOG( "Gv::HttpServerPeer::onReceive: http request: [" << G::Str::printable(line) << "]" ) ;
91  m_url = G::Url(part[1U]) ;
92 
93  m_config = m_config_default ;
94  m_config.init( m_url ) ;
95 
96  m_state = s_got_get ;
97  m_content_length = 0U ;
98  selectSource( m_url.path() ) ;
99  G_DEBUG( "Gv::HttpServerPeer::onReceive: got get" ) ;
100  }
101  else if( ( m_state == s_init || m_state == s_idle ) && line.find("POST") == 0U )
102  {
103  G::StringArray part ;
104  G::Str::splitIntoTokens( line , part , " \t" ) ;
105  part.push_back( std::string() ) ;
106  m_url = G::Url(part[1U]) ;
107  if( m_config.moreVerbose() )
108  G_LOG( "Gv::HttpServerPeer::onReceive: http request: [" << G::Str::printable(line) << "]" ) ;
109 
110  m_state = s_got_post ;
111  m_content_length = 0U ;
112  selectSource( m_url.path() ) ;
113  }
114  else if( m_state == s_got_post && !line.empty() )
115  {
116  // 'post' header line
117  if( G::Str::ifind(line,"Content-Length:") == 0U )
118  m_content_length = headerValue( line ) ;
119  }
120  else if( m_state == s_got_post && m_content_length )
121  {
122  m_state = s_got_post_headers ;
123  expect( m_content_length ) ; // GNet::BufferedServerPeer
124  m_content_length = 0U ;
125  }
126  else if( m_state == s_got_post || m_state == s_got_post_headers )
127  {
128  m_state = s_idle ;
129  if( !line.empty() && m_config.moreVerbose() )
130  G_LOG( "Gv::HttpServerPeer::onReceive: got post request body: [" << G::Str::printable(line) << "]" ) ;
131  doPost() ;
132  }
133  else if( m_state == s_got_get && !line.empty() )
134  {
135  // 'get' header line
136  if( G::Str::ifind(line,"Content-Length:") == 0U )
137  m_content_length = headerValue( line ) ;
138  }
139  else if( m_state == s_got_get && m_content_length )
140  {
141  m_state = s_got_get_headers ;
142  expect( m_content_length ) ; // GNet::BufferedServerPeer
143  m_content_length = 0U ;
144  }
145  else if( ( m_state == s_got_get || m_state == s_got_get_headers ) )
146  {
147  if( !line.empty() && m_config.moreVerbose() )
148  G_LOG( "Gv::HttpServerPeer::onReceive: got get request body: [" << G::Str::printable(line) << "]" ) ;
149 
150  if( m_url.has("send") )
151  doGatewayMessage( m_url.parameter("send") ) ;
152 
153  if( fileRequest() || specialRequest() )
154  {
155  m_state = s_file ;
156  m_data_timer.startTimer( 0 ) ;
157  }
158  else if( m_source.get() == nullptr )
159  {
160  m_state = s_idle ;
161  G_DEBUG( "Gv::HttpServerPeer::onReceive: http request for invalid channel" ) ;
162  doSendResponse( 404 , "No such channel" ) ;
163  }
164  else if( m_config.type() == "jpeg" && !Gr::Jpeg::available() )
165  {
166  m_state = s_idle ;
167  G_WARNING( "Gv::HttpServerPeer::onReceive: http request for jpeg but no libjpeg built in" ) ;
168  doSendResponse( 415 , "Unsupported media type" ) ; // TODO support http content negotiation
169  }
170  else if( m_image.empty() )
171  {
172  m_state = s_waiting ;
173  G_DEBUG( "Gv::HttpServerPeer::onReceive: image not yet available: "
174  << "waiting up to " << m_config.firstImageTimeout() << "s" ) ;
175  if( m_config.quick() ) m_source.resend() ;
176  m_data_timer.startTimer( m_config.firstImageTimeout() ) ;
177  }
178  else if( m_config.streaming() )
179  {
180  m_state = s_streaming_first_idle ;
181  if( m_config.quick() ) m_source.resend() ;
182  m_data_timer.startTimer( 0 ) ;
183  }
184  else
185  {
186  m_state = s_single_idle ;
187  if( m_config.quick() ) m_source.resend() ;
188  m_data_timer.startTimer( 0 ) ;
189  }
190  }
191  else
192  {
193  G_DEBUG( "Gv::HttpServerPeer::onReceive: ignoring unexpected data in state " << m_state ) ;
194  }
195  return true ;
196 }
197 
198 void Gv::HttpServerPeer::buildPduFirst()
199 {
200  G_ASSERT( !m_image.empty() ) ;
201  m_pdu.clear() ;
202  m_pdu.append( streamingHeader(m_image_size,m_image.type(),m_image_type_str) ) ;
203  m_pdu.append( streamingSubHeader(m_image_size,m_image.type(),m_image_type_str) ) ;
204  m_pdu.assignBody( m_image.ptr() , m_image_size ) ;
205 }
206 
207 void Gv::HttpServerPeer::buildPdu()
208 {
209  G_ASSERT( !m_image.empty() ) ;
210  m_pdu.clear() ;
211  m_pdu.append( streamingSubHeader(m_image_size,m_image.type(),m_image_type_str) ) ;
212  m_pdu.assignBody( m_image.ptr() , m_image_size ) ;
213 }
214 
215 void Gv::HttpServerPeer::buildPduSingle()
216 {
217  G_ASSERT( !m_image.empty() ) ;
218  m_pdu.clear() ;
219  m_pdu.append( simpleHeader(m_image_size,m_image.type(),m_image_type_str,m_source.name(),m_source.info()) ) ;
220  m_pdu.assignBody( m_image.ptr() , m_image_size ) ;
221 }
222 
223 bool Gv::HttpServerPeer::sendPdu()
224 {
225  m_sending = 0U ;
226  if( m_config.moreVerbose() )
227  G_LOG( "Gv::HttpServerPeer::sendPdu: sending image: " << m_pdu.size() << " bytes" ) ;
228  bool sent = doSend( m_pdu ) ;
229  if( !sent && m_config.moreVerbose() )
230  G_LOG( "Gv::HttpServerPeer::sendPdu: flow control asserted for image " << m_image_number ) ;
231  return sent ;
232 }
233 
234 bool Gv::HttpServerPeer::doSend( const Pdu & pdu )
235 {
236  doSendLogging( pdu ) ;
237  bool all_sent = send( pdu.segments() ) ; // GNet::ServerPeer::send()
238  if( !all_sent )
239  pdu.lock() ;
240  return all_sent ;
241 }
242 
243 bool Gv::HttpServerPeer::doSend( const std::string & s )
244 {
245  doSendLogging( Pdu(s) ) ;
246  return send( s ) ; // GNet::ServerPeer::send()
247 }
248 
249 void Gv::HttpServerPeer::doSendLogging( const Pdu & pdu ) const
250 {
251  if( G::Log::at(G::Log::s_LogVerbose) && m_config.moreVerbose() )
252  {
253  typedef std::string::const_iterator Ptr ;
254  std::string s = pdu.head() ; // just the headers
255  Ptr old = s.begin() ;
256  Ptr const end = s.end() ;
257  for( Ptr pos = std::find(old,end,'\n') ; pos != end ; old=pos+1 , pos = std::find(pos+1,end,'\n') )
258  {
259  std::string line = pos != s.begin() ? std::string(old,pos) : std::string() ;
260  G::Str::trimRight( line , "\r" ) ;
261  G_LOG( "Gv::HttpServerPeer::doSend: tx>>: \"" << G::Str::printable(line) << "\"" ) ;
262  }
263  }
264 }
265 
266 void Gv::HttpServerPeer::doPost()
267 {
268  std::string reason = doGatewayMessageImp( m_url.parameter("send") ) ;
269  if( reason.empty() )
270  doSendResponse( 200 , "OK" ) ;
271  else
272  doSendResponse( 500 , reason ) ;
273 }
274 
275 void Gv::HttpServerPeer::doGatewayMessage( const std::string & send_param )
276 {
277  std::string reason = doGatewayMessageImp( send_param ) ;
278  if( !reason.empty() )
279  G_WARNING( "Gv::HttpServerPeer::doGatewayMessage: gateway message failed: " << reason ) ;
280 }
281 
282 std::string Gv::HttpServerPeer::doGatewayMessageImp( const std::string & send_param )
283 {
284  G_LOG( "Gv::HttpServerPeer::doGatewayMessage: send=[" << send_param << "]" ) ;
285 
286  if( send_param.empty() )
287  return "no 'send' parameter: nothing to do" ;
288 
289  if( m_config.gateway() == GNet::Address::defaultAddress() )
290  return "no gateway address configured" ;
291 
292  // parse parameter value as "port <url-encoded-message>"
293  unsigned int port = G::Str::toUInt( G::Str::head(send_param," ") , "0" ) ;
294  if( port == 0U )
295  return "incorrect 'send' parameter: no port value" ;
296 
297  std::string payload = G::Str::tail( send_param , " " ) ; // already urldecoded by G::Url::parameter()
298  if( payload.empty() )
299  return "incorrect 'send' parameter: no message part" ;
300 
301  // create the socket
302  GNet::DatagramSocket socket( m_config.gateway().domain() ) ;
303 
304  // send the message
305  GNet::Address address( m_config.gateway() ) ;
306  address.setPort( port ) ;
307  ssize_t n = socket.writeto( payload.data() , payload.size() , address ) ;
308  G_LOG( "Gv::HttpServerPeer::doGatewayMessage: gateway: "
309  << "host=[" << m_config.gateway().hostPartString() << "] "
310  << "port=[" << port << "] "
311  << "payload=[" << G::Str::printable(payload) << "] "
312  << "sent=" << n << "/" << payload.size() ) ;
313  if( n != static_cast<ssize_t>(payload.size()) )
314  return "udp send failed" ;
315 
316  return std::string() ;
317 }
318 
319 bool Gv::HttpServerPeer::fileRequest()
320 {
321  return m_resources.fileResource( m_url.path() ) ;
322 }
323 
324 bool Gv::HttpServerPeer::specialRequest()
325 {
326  return m_resources.specialResource( m_url.path() ) ;
327 }
328 
329 void Gv::HttpServerPeer::buildPduFromFile()
330 {
331  std::pair<std::string,std::string> pair = m_resources.fileResourcePair( m_url.path() ) ;
332  std::string path = pair.first ;
333  std::string type = pair.second ;
334 
335  m_pdu.clear() ;
336  std::ifstream file ;
337  if( !path.empty() )
338  {
339  G_DEBUG( "Gv::HttpServerPeer::buildPduFromFile: url-path=[" << m_url.path() << "] file-path=[" << path << "] type=[" << type << "]" ) ;
340  {
341  G::Root claim_root ;
342  file.open( path.c_str() ) ;
343  }
344  if( file.good() )
345  {
346  // read the whole file
347  shared_ptr<Gr::ImageBuffer> image_buffer_ptr( new Gr::ImageBuffer ) ;
348  file >> *image_buffer_ptr ;
349  if( file.fail() )
350  {
351  G_WARNING( "Gv::HttpServerPeer::buildPduFromFile: cannot read file [" << G::Str::printable(path) << "]" ) ;
352  }
353  else
354  {
355  size_t file_size = Gr::imagebuffer::size_of( *image_buffer_ptr ) ;
356  m_pdu.append( fileHeader(file_size,type) ) ;
357  m_pdu.assignBody( image_buffer_ptr , file_size ) ;
358  if( m_config.moreVerbose() )
359  G_LOG( "Gv::HttpServerPeer::buildPduFromFile: serving file: path=[" << path << "] type=[" << type << "] size=" << file_size ) ;
360  }
361  }
362  else
363  {
364  G_WARNING( "Gv::HttpServerPeer::buildPduFromFile: cannot open file [" << G::Str::printable(path) << "]" ) ;
365  }
366  }
367  else
368  {
369  const std::string & reason = pair.second ;
370  G_LOG( "Gv::HttpServerPeer::buildPduFromFile: url-path=[" << m_url.path() << "]: resource not available: " << reason ) ;
371  }
372 
373  if( m_pdu.empty() )
374  {
375  m_pdu = errorResponse( 404 , "Not Found" ) ;
376  }
377 }
378 
379 void Gv::HttpServerPeer::buildPduFromStatus()
380 {
381  G::Item info = G::Item::map() ;
383  for( G::StringArray::iterator p = list.begin() ; p != list.end() ; ++p )
384  {
385  info.add( *p , G::Publisher::info( *p ) ) ;
386  }
387  std::ostringstream ss ;
388  info.out( ss ) ;
389  size_t ss_size = static_cast<size_t>(std::max(std::streampos(0),ss.tellp())) ;
390  m_pdu.clear() ;
391  m_pdu.append( fileHeader(ss_size,"application/json") ) ;
392  m_pdu.append( ss.str() ) ;
393 }
394 
395 void Gv::HttpServerPeer::onDataTimeout()
396 {
397  // the data timer is short-circuited by a new video image (onImageInput())
398  // but it is also used to ensure a minimum frame rate, typically 1fps,
399  // when streaming
400 
401  G_DEBUG( "Gv::HttpServerPeer::onDataTimeout: image timeout: state " << m_state ) ;
402  if( m_state == s_file )
403  {
404  if( fileRequest() )
405  buildPduFromFile() ;
406  else
407  buildPduFromStatus() ;
408 
409  bool all_sent = doSend( m_pdu ) ;
410  if( all_sent )
411  m_state = s_idle ;
412  else
413  m_state = s_file_busy ;
414  }
415  else if( m_state == s_waiting )
416  {
417  if( m_image.empty() )
418  {
419  m_state = s_idle ;
420  G_DEBUG( "Gv::HttpServerPeer::onDataTimeout: no image available for http get request [" + m_url.str() + "]" ) ;
421  doSendResponse( 503 , "Image unavailable" , "Retry-After: 1" ) ;
422  }
423  else if( m_config.streaming() )
424  {
425  m_state = s_streaming_first_idle ;
426  m_data_timer.startTimer( 0 ) ;
427  }
428  else
429  {
430  m_state = s_single_idle ;
431  m_data_timer.startTimer( 0 ) ;
432  }
433  }
434  else if( m_state == s_streaming_first_idle )
435  {
436  m_idle_timer.cancelTimer() ;
437  buildPduFirst() ;
438  bool all_sent = sendPdu() ;
439  if( all_sent )
440  {
441  m_state = s_streaming_idle ;
442  m_data_timer.startTimer( m_config.imageRepeatTimeout() ) ;
443  }
444  else
445  {
446  m_state = s_streaming_first_busy ;
447  m_sending = m_image_number ;
448  }
449  }
450  else if( m_state == s_streaming_idle )
451  {
452  m_idle_timer.cancelTimer() ;
453  buildPdu() ;
454  bool all_sent = sendPdu() ;
455  if( all_sent )
456  {
457  m_state = s_streaming_idle ;
458  m_data_timer.startTimer( m_config.imageRepeatTimeout() ) ;
459  }
460  else
461  {
462  m_state = s_streaming_busy ;
463  m_sending = m_image_number ;
464  }
465  }
466  else if( m_state == s_single_idle )
467  {
468  buildPduSingle() ;
469  bool all_sent = sendPdu() ;
470  if( all_sent )
471  {
472  m_state = s_idle ;
473  }
474  else
475  {
476  m_state = s_single_busy ;
477  m_sending = m_image_number ;
478  }
479  }
480 }
481 
482 void Gv::HttpServerPeer::onSendComplete()
483 {
484  G_DEBUG( "Gv::HttpServerPeer::onSendComplete: image " << m_sending << " sent" ) ;
485  if( m_config.moreVerbose() )
486  G_LOG( "Gv::HttpServerPeer::onSendComplete: flow control released for image " << m_sending ) ;
487  m_pdu.release() ;
488  if( m_state == s_idle )
489  {
490  }
491  else if( m_state == s_streaming_first_busy )
492  {
493  m_state = s_streaming_idle ;
494  startStreamingTimer() ;
495  m_sending = 0U ;
496  }
497  else if( m_state == s_streaming_busy )
498  {
499  m_state = s_streaming_idle ;
500  startStreamingTimer() ;
501  m_sending = 0U ;
502  }
503  else if( m_state == s_single_busy )
504  {
505  m_state = s_idle ;
506  }
507  else if( m_state == s_file_busy )
508  {
509  m_state = s_idle ;
510  }
511 }
512 
513 void Gv::HttpServerPeer::startStreamingTimer()
514 {
515  G_ASSERT( m_sending != 0U ) ;
516  bool sent_image_is_latest = m_sending == m_image_number ;
517  if( sent_image_is_latest ) // network output is faster than the camera input - wait for the camera
518  m_data_timer.startTimer( m_config.imageRepeatTimeout() ) ;
519  else
520  m_data_timer.startTimer( 0 ) ;
521 }
522 
523 Gr::Image Gv::HttpServerPeer::textToJpeg( const Gr::ImageBuffer & text_buffer )
524 {
525  Gr::ImageBuffer * image_buffer = Gr::Image::blank( m_text_raw_image , Gr::ImageType::raw(160,120,1) ) ;
526  Gr::ImageData image_data( *image_buffer , 160 , 120 , 1 ) ;
527  image_data.fill( 0 , 0 , 0 ) ;
528 
530  Gr::ImageDataWriter writer( image_data , 0 , 0 , Gr::Colour(255U,255U,255U) , Gr::Colour(0,0,0) , true , false ) ;
531  writer.write( iterator_t(text_buffer) , iterator_t() ) ;
532 
533  m_sources.converter().toJpeg( m_text_raw_image , m_text_jpeg_image ) ;
534 
535  return m_text_jpeg_image ;
536 }
537 
538 void Gv::HttpServerPeer::onNonImageInput( ImageInputSource & , Gr::Image image , const std::string & type_str )
539 {
540  G_ASSERT( !image.empty() && !image.valid() ) ; // we have data but it's not an image
541  G_DEBUG( "Gv::HttpServerPeer::onNonImageInput: non-image [" << type_str << "]" ) ;
542 
543  if( m_config.type() == "any" )
544  {
545  doInput( image , type_str ) ;
546  }
547  else if( type_str == "application/json" && Gr::Jpeg::available() )
548  {
549  image = textToJpeg( image.data() ) ;
550  doInput( image , image.type().str() ) ;
551  }
552  else
553  {
554  G_DEBUG( "Gv::HttpServerPeer::onNonImageInput: not serving non-image" ) ;
555  }
556 }
557 
558 void Gv::HttpServerPeer::onImageInput( ImageInputSource & , Gr::Image image )
559 {
560  G_DEBUG( "Gv::HttpServerPeer::onImageInput: type=[" << image.type() << "](" << image.type() << ") seqno=[" << m_image_number << "]" ) ;
561  doInput( image , image.type().str() ) ;
562 }
563 
564 void Gv::HttpServerPeer::doInput( Gr::Image image , const std::string & type_str )
565 {
566  m_image_number++ ;
567  if( m_image_number == 0U )
568  m_image_number = 1U ;
569 
570  if( m_state == s_init || m_state == s_idle )
571  {
572  // in the init and idle states we do not know the client's requested type so we
573  // have not told the input class what image type and size to convert to
574  m_image.clear() ;
575  }
576  else
577  {
578  m_image = image ;
579  m_image_size = Gr::imagebuffer::size_of(image.data()) ;
580  m_image_type_str = type_str ;
581  if( m_state == s_waiting || m_state == s_streaming_idle )
582  m_data_timer.startTimer( 0 ) ; // short-circuit
583  }
584 }
585 
586 Gv::ImageInputConversion Gv::HttpServerPeer::imageInputConversion( ImageInputSource & )
587 {
588  ImageInputConversion conversion ;
589  conversion.scale = m_config.scale() ;
590  conversion.monochrome = m_config.monochrome() ;
591 
592  if( m_config.type() == "raw" || m_config.type() == "pnm" )
593  conversion.type = ImageInputConversion::to_raw ;
594  else if( m_config.type() == "any" || !Gr::Jpeg::available() )
595  conversion.type = ImageInputConversion::none ;
596  else
597  conversion.type = ImageInputConversion::to_jpeg ;
598  return conversion ;
599 }
600 
601 void Gv::HttpServerPeer::onDelete( const std::string & reason )
602 {
603  if( !reason.empty() && reason.find("peer disconnected") != 0U )
604  G_ERROR( "Gv::HttpServerPeer::onException: exception: " << reason ) ;
605  G_LOG( "Gv::HttpServerPeer::onDelete: disconnection of " << peerAddress().second.displayString() ) ;
606 }
607 
608 void Gv::HttpServerPeer::onIdleTimeout()
609 {
610  G_LOG( "Gv::HttpServerPeer::onIdleTimeout: timeout" ) ;
611  doDelete() ;
612 }
613 
614 void Gv::HttpServerPeer::doSendResponse( int e , const std::string & s , const std::string & header )
615 {
616  doSend( errorResponse(e,s,header) ) ;
617 }
618 
619 std::string Gv::HttpServerPeer::errorResponse( int e , const std::string & s , const std::string & header ) const
620 {
621  std::string body ;
622  {
623  std::ostringstream ss ;
624  ss << "<!DOCTYPE html>\r\n<html><body><p>" << e << " " << s << "</p></body></html>\r\n" ;
625  body = ss.str() ;
626  }
627 
628  std::ostringstream ss ;
629  ss
630  << "HTTP/1.1 " << e << " " << s << "\r\n"
631  << "Content-Length: " << body.size() << "\r\n"
632  << "Content-Type: text/html\r\n" ;
633  if( !header.empty() )
634  ss << header << "\r\n" ;
635  ss
636  << "Connection: keep-alive\r\n"
637  << "\r\n"
638  << body ;
639 
640  return ss.str() ;
641 }
642 
643 std::string Gv::HttpServerPeer::fileHeader( size_t content_length , const std::string & content_type ) const
644 {
645  std::ostringstream ss ;
646  ss
647  << "HTTP/1.1 200 OK\r\n" ;
648  if( !content_type.empty() )
649  ss << "Content-Type: " << content_type << "\r\n" ;
650  ss
651  << "Content-Length: " << content_length << "\r\n"
652  << "Connection: keep-alive\r\n"
653  << "\r\n" ;
654  return ss.str() ;
655 }
656 
657 std::string Gv::HttpServerPeer::pnmHeader( Gr::ImageType type ) const
658 {
659  std::ostringstream ss ;
660  if( type.channels() == 1 )
661  ss << "P5\n" ;
662  else
663  ss << "P6\n" ;
664  ss << type.dx() << " " << type.dy() << "\n255\n" ;
665  return ss.str() ;
666 }
667 
668 std::string Gv::HttpServerPeer::pnmType( Gr::ImageType type ) const
669 {
670  return "image/x-portable-anymap" ;
671 }
672 
673 std::string Gv::HttpServerPeer::simpleHeader( size_t content_length , Gr::ImageType type ,
674  const std::string & type_str , const std::string & source_name , const std::string & source_info ) const
675 {
676  std::string content_type = type.valid() ? ( type.isRaw() ? type.str() : type.simple() ) : type_str ;
677 
678  std::string pnm_header ;
679  if( type.isRaw() && m_config.type() == "pnm" )
680  {
681  pnm_header = pnmHeader( type ) ;
682  content_type = pnmType( type ) ;
683  content_length += pnm_header.size() ;
684  }
685 
686  std::ostringstream ss ;
687  ss << "HTTP/1.1 200 OK\r\n" ;
688  if( m_config.refresh() )
689  ss << "Refresh: " << m_config.refresh() << "\r\n" ;
690  ss
691  << "Content-Type: " << content_type << "\r\n"
692  << "Content-Length: " << content_length << "\r\n"
693  << "Cache-Control: no-cache\r\n"
694  << "Connection: keep-alive\r\n" ;
695 
696  std::string appid( "VT-" ) ;
697  if( appid.find("__") == 0U )
698  appid.clear() ;
699 
700  if( type.valid() && type.dx() != 0 )
701  ss << "X-" << appid << "Width: " << type.dx() << "\r\n" ;
702  if( type.valid() && type.dy() != 0 )
703  ss << "X-" << appid << "Height: " << type.dy() << "\r\n" ;
704  if( !source_name.empty() )
705  ss << "X-" << appid << "Source: " << G::Url::encode(source_name) << "\r\n" ;
706  if( !source_info.empty() )
707  ss << "X-" << appid << "Source-Info: " << G::Url::encode(source_info,false) << "\r\n" ;
708 
709  ss << "\r\n" << pnm_header ;
710  return ss.str() ;
711 }
712 
713 namespace
714 {
715  const char * boundary = "29872987349876236436234298656398659642596" ;
716 }
717 
718 std::string Gv::HttpServerPeer::streamingHeader( size_t , Gr::ImageType type , const std::string & ) const
719 {
720  std::ostringstream ss ;
721  ss
722  << "HTTP/1.1 200 OK\r\n"
723  << "Connection: keep-alive\r\n" ;
724  if( m_config.refresh() )
725  ss << "Refresh: " << m_config.refresh() << "\r\n" ; // dubious for streaming, but helps fault tolerance
726  ss
727  << "Content-Type: multipart/x-mixed-replace;boundary=" << boundary << "\r\n" ;
728 
729  std::string appid( "VT-" ) ;
730  if( appid.find("__") == 0U )
731  appid.clear() ;
732 
733  if( type.valid() && type.dx() != 0 )
734  ss << "X-" << appid << "Width: " << type.dx() << "\r\n" ;
735  if( type.valid() && type.dy() != 0 )
736  ss << "X-" << appid << "Height: " << type.dy() << "\r\n" ;
737 
738  ss
739  << "\r\n" ;
740  return ss.str() ;
741 }
742 
743 std::string Gv::HttpServerPeer::streamingSubHeader( size_t content_length , Gr::ImageType type , const std::string & type_str ) const
744 {
745  std::string content_type = type.valid() ? ( type.isRaw() ? type.str() : type.simple() ) : type_str ;
746 
747  std::string pnm_header ;
748  if( type.isRaw() && m_config.type() == "pnm" )
749  {
750  pnm_header = pnmHeader( type ) ;
751  content_type = pnmType( type ) ;
752  content_length += pnm_header.size() ;
753  }
754 
755  std::ostringstream ss ;
756  ss
757  << "\r\n--" << boundary << "\r\n"
758  << "Content-Type: " << content_type << "\r\n"
759  << "Content-Length: " << content_length << "\r\n"
760  << "\r\n" << pnm_header ;
761  return ss.str() ;
762 }
763 
764 unsigned int Gv::HttpServerPeer::headerValue( const std::string & line )
765 {
766  std::string value = G::Str::trimmed( G::Str::tail(line,":") , G::Str::ws() ) ;
767  if( value.empty() || !G::Str::isUInt(value) )
768  G_DEBUG( "Gv::HttpServerPeer::headerValue: not a number: [" << line << "]" ) ;
769  return G::Str::toUInt( value , "0" ) ;
770 }
771 
772 // ==
773 
774 Gv::HttpServerPeer::Pdu::Pdu() :
775  m_body_ptr_size(0U) ,
776  m_locked(false)
777 {
778  m_segments.reserve( 3U ) ;
779 }
780 
781 Gv::HttpServerPeer::Pdu::Pdu( const std::string & s ) :
782  m_head(s) ,
783  m_body_ptr_size(0U) ,
784  m_locked(false)
785 {
786 }
787 
788 void Gv::HttpServerPeer::Pdu::clear()
789 {
790  G_ASSERT( m_body_ptr.get() == nullptr || !m_locked ) ;
791  m_body_ptr.reset() ;
792  m_body_ptr_size = 0U ;
793  m_head.clear() ;
794 }
795 
796 bool Gv::HttpServerPeer::Pdu::empty() const
797 {
798  return m_head.empty() && m_body_ptr_size == 0U ;
799 }
800 
801 void Gv::HttpServerPeer::Pdu::append( const std::string & s )
802 {
803  G_ASSERT( m_body_ptr.get() == nullptr ) ; // moot
804  m_head.append( s ) ;
805 }
806 
807 void Gv::HttpServerPeer::Pdu::assignBody( shared_ptr<const Gr::ImageBuffer> data_ptr , size_t n )
808 {
809  G_ASSERT( m_body_ptr.get() == nullptr || !m_locked ) ;
810  m_body_ptr = data_ptr ;
811  m_body_ptr_size = n ;
812 }
813 
814 size_t Gv::HttpServerPeer::Pdu::size() const
815 {
816  return m_head.size() + m_body_ptr_size ;
817 }
818 
819 std::string Gv::HttpServerPeer::Pdu::head() const
820 {
821  return m_head ;
822 }
823 
824 void Gv::HttpServerPeer::Pdu::operator=( const std::string & s )
825 {
826  m_body_ptr.reset() ;
827  m_body_ptr_size = 0U ;
828  m_head = s ;
829 }
830 
831 void Gv::HttpServerPeer::Pdu::lock() const
832 {
833  G_ASSERT( !m_locked ) ;
834  m_locked = true ;
835 }
836 
837 void Gv::HttpServerPeer::Pdu::release()
838 {
839  G_ASSERT( m_locked ) ;
840  m_locked = false ;
841  m_body_ptr.reset() ;
842  m_body_ptr_size = 0U ;
843 }
844 
845 const std::vector<std::pair<const char *,size_t> > & Gv::HttpServerPeer::Pdu::segments() const
846 {
847  m_segments.clear() ;
848  if( !m_head.empty() ) m_segments.push_back( Segment(m_head.data(),m_head.size()) ) ;
849  if( m_body_ptr_size != 0U )
850  {
851  const Gr::ImageBuffer & image_buffer = *m_body_ptr.get() ;
852 
854  for( row_iterator part_p = Gr::imagebuffer::row_begin(image_buffer) ; part_p != Gr::imagebuffer::row_end(image_buffer) ; ++part_p )
855  {
856  const char * segment_p = Gr::imagebuffer::row_ptr(part_p) ;
857  size_t segment_n = Gr::imagebuffer::row_size(part_p) ;
858  m_segments.push_back( Segment(segment_p,segment_n) ) ;
859  }
860  }
861  return m_segments ;
862 }
863 
864 /// \file gvhttpserverpeer.cpp
bool isRaw() const
Returns true if a raw image type.
A configuration structure for Gv::HttpServerPeer holding default settings from the command-line which...
Definition: gvhttpserver.h:275
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:663
A traits class that can be specialised for Gr::ImageBuffer candidates.
Definition: grtraits.h:34
A holder for image data, having eight bits per sample and one or three channels.
Definition: grimagedata.h:46
void out(std::ostream &s, int indent=-1) const
Does output streaming, using a json-like format.
Definition: gitem.cpp:249
static bool at(Severity)
Returns true if G::LogOutput::output() would log at the given level.
Definition: glog.cpp:43
HttpServerPeer(GNet::Server::PeerInfo, Sources &, const Resources &, const Config &)
Constructor.
int channels() const
Returns the number of channels.
Definition: grimagetype.h:197
Synopsis:
static Item map()
Factory function for a map item.
Definition: gitem.cpp:33
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:55
static std::vector< std::string > list(std::vector< std::string > *others=nullptr)
Returns a list of channel names.
Definition: gpublisher.cpp:288
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
A derivation of GNet::Socket for a datagram socket.
Definition: gsocket.h:279
An encapsulation of image type, including width, height and number of channels, with support for a st...
Definition: grimagetype.h:43
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:49
static void splitIntoTokens(const std::string &in, StringArray &out, const std::string &ws)
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:868
A simple rgb colour structure.
Definition: grcolour.h:36
Vectors ImageBuffer
An ImageBuffer is used to hold raw image data, typically in more than one chunk.
Definition: grimagebuffer.h:47
A simple parser for URLs.
Definition: gurl.h:42
static std::string tail(const std::string &in, std::string::size_type pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1051
static ImageType raw(int dx, int dy, int channels)
Factory function for a raw image type.
bool empty() const
Returns true if constructed with no image data.
Definition: grimage.cpp:90
std::string str() const
Returns the image type string (including the size parameter).
A class holding shared read-only image data (Gr::ImageBuffer) and its associated image type (Gr::Imag...
Definition: grimage.h:41
bool valid() const
Returns true if valid.
unsigned int idleTimeout() const
Returns the connection idle timeout.
static bool available()
Returns true if a jpeg library is available.
static unsigned int toUInt(const std::string &s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:450
static ImageBuffer * blank(Image &, ImageType raw_type, bool contiguous=false)
Factory function for a not-really-blank raw image that is temporarily writable via the returned image...
Definition: grimage.cpp:54
static Address defaultAddress()
Returns a default address, being the IPv4 wildcard address with a zero port number.
int dy() const
Returns the image height.
Definition: grimagetype.h:191
static std::string head(const std::string &in, std::string::size_type pos, const std::string &default_=std::string())
Returns the first part of the string up to just before the given position.
Definition: gstr.cpp:1037
static void trimRight(std::string &s, const std::string &ws, size_type limit=0U)
Trims the rhs of s, taking off up to 'limit' of the 'ws' characters.
Definition: gstr.cpp:197
bool valid() const
Returns !empty() && type().valid().
Definition: grimage.cpp:80
ImageType type() const
Returns the image type.
Definition: grimage.cpp:85
static std::string trimmed(const std::string &s, const std::string &ws)
Returns a trim()med version of s.
Definition: gstr.cpp:213
const ImageBuffer & data() const
Returns the image data.
Definition: grimage.cpp:112
A variant class holding a string, an item map keyed by name, or an ordered list of items...
Definition: gitem.h:41
int dx() const
Returns the image width.
Definition: grimagetype.h:185
A GNet::ServerPeer class for HTTP servers that serves up image streams.
void add(const std::string &s)
Adds a string item to this list item.
Definition: gitem.cpp:108
static Item info(const std::string &channel_name, bool all_slots=true)
Returns a variant containing information about the state of the channel.
Definition: gpublisher.cpp:293
std::string simple() const
Returns the basic image type string, excluding the size parameter.
virtual ~HttpServerPeer()
Destructor.
A container for ImageInputSource pointers, used by Gv::HttpServerPeer.
Definition: gvhttpserver.h:122
A configuration structure for resources that Gv::HttpServerPeer makes available.
Definition: gvhttpserver.h:169
A structure that describes the preferred image type for the Gv::ImageInputHandler callback...
Definition: gvimageinput.h:47
static bool isUInt(const std::string &s)
Returns true if the string can be converted into an unsigned integer without throwing an exception...
Definition: gstr.cpp:266
static std::string::size_type ifind(const std::string &s, const std::string &key, std::string::size_type pos=0U)
Does a case-insensitive std::string::find().
Definition: gstr.cpp:1112
void setPort(unsigned int port)
Sets the port number.
static std::string encode(const std::string &, bool plus_for_space=true)
Does url-encoding.
Definition: gurl.cpp:279
A structure used in GNet::Server::newPeer().
Definition: gserver.h:86
static std::string ws()
A convenience function returning standard whitespace characters.
Definition: gstr.cpp:1027