00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038 #include "connection.h"
00039
00040 #include <string>
00041 #include <vector>
00042 #include <fstream>
00043 #include <sstream>
00044
00045 #include <boost/bind.hpp>
00046 #include <boost/foreach.hpp>
00047 #include <boost/lexical_cast.hpp>
00048 #include <boost/asio.hpp>
00049 #include <boost/any.hpp>
00050 #include <boost/algorithm/string.hpp>
00051 #include <boost/date_time/posix_time/posix_time.hpp>
00052 #include <boost/date_time/posix_time/posix_time_io.hpp>
00053
00054 #include "ros/master.h"
00055 #include <sensor_msgs/image_encodings.h>
00056 #include <image_transport/image_transport.h>
00057 #include <image_transport/subscriber_filter.h>
00058
00059 #define HTTP_TRANSFER_ENCODING
00060
00061 namespace ros_http_video_streamer
00062 {
00063
00064 namespace misc_strings {
00065
00066 const char name_value_separator[] = { ':', ' ' };
00067 const char crlf[] = { '\r', '\n' };
00068
00069 #ifdef HTTP_TRANSFER_ENCODING
00070 const std::string ok =
00071 "HTTP/1.1 200 OK\r\n";
00072
00073 }
00074 #else
00075 const std::string ok =
00076 "HTTP/1.0 200 OK\r\n";
00077
00078 }
00079 #endif
00080
00081 struct mime_map
00082 {
00083 const char* extension;
00084 const char* mime_type;
00085 } mime_mapping[] =
00086 {
00087 { "gif", "image/gif" },
00088 { "htm", "text/html" },
00089 { "html", "text/html" },
00090 { "jpg", "image/jpeg" },
00091 { "png", "image/png" },
00092 { "css", "text/css" },
00093 { "js", "text/javascript" },
00094 { 0, 0 }
00095 };
00096
00097 connection::connection(boost::asio::io_service& io_service,
00098 EncoderManager& encoder_manager,
00099 const ServerConfiguration& default_server_conf)
00100 : server_conf_(default_server_conf),
00101 image_topics_(),
00102 strand_(io_service),
00103 socket_(io_service),
00104 buffer_(),
00105 request_(),
00106 request_parser_(),
00107 reply_(),
00108 encoder_manager_(encoder_manager),
00109 streaming_thread_(),
00110 do_streaming_(false)
00111 {
00112 }
00113
00114 connection::~connection()
00115 {
00116 do_streaming_ = false;
00117
00118 if (streaming_thread_)
00119 streaming_thread_->join();
00120
00121 }
00122
00123
00124
00125
00126 static boost::asio::const_buffer create_cstr_asio_buffer(const char* c_str)
00127 {
00128 return boost::asio::buffer(c_str, strlen(c_str));
00129 }
00130
00131
00132 void connection::sendHTTPStreamingHeaders()
00133 {
00134 std::vector<boost::asio::const_buffer> buffers;
00135
00136 buffers.push_back(boost::asio::buffer(misc_strings::ok));
00137
00138 buffers.push_back(boost::asio::buffer("Date"));
00139 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00140
00141 std::ostringstream datetime_ss;
00142 boost::posix_time::time_facet * p_time_output = new boost::posix_time::time_facet;
00143 std::locale special_locale (std::locale(""), p_time_output);
00144 datetime_ss.imbue (special_locale);
00145 (*p_time_output).format("%a, %d %b %Y %H:%M:%S%F %z");
00146 datetime_ss << boost::posix_time::second_clock::local_time();
00147
00148 buffers.push_back(boost::asio::buffer(datetime_ss.str()));
00149 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00150
00151 buffers.push_back(create_cstr_asio_buffer("Content-Type"));
00152 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00153 buffers.push_back(create_cstr_asio_buffer("video/webm"));
00154 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00155
00156
00157 buffers.push_back(create_cstr_asio_buffer("Cache-Control"));
00158 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00159 buffers.push_back(create_cstr_asio_buffer("no-cache"));
00160 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00161
00162 buffers.push_back(create_cstr_asio_buffer("Connection"));
00163 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00164 buffers.push_back(create_cstr_asio_buffer("Close"));
00165 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00166
00167 buffers.push_back(create_cstr_asio_buffer("Access-Control-Allow-Origin"));
00168 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00169 buffers.push_back(create_cstr_asio_buffer("*"));
00170 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00171
00172 buffers.push_back(create_cstr_asio_buffer("Pragma"));
00173 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00174 buffers.push_back(create_cstr_asio_buffer("no-cache"));
00175 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00176
00177 buffers.push_back(create_cstr_asio_buffer("Expires"));
00178 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00179 buffers.push_back(create_cstr_asio_buffer("0"));
00180 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00181
00182 buffers.push_back(create_cstr_asio_buffer("Max-Age"));
00183 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00184 buffers.push_back(create_cstr_asio_buffer("0"));
00185 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00186
00187 buffers.push_back(create_cstr_asio_buffer("Trailer"));
00188 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00189 buffers.push_back(create_cstr_asio_buffer("Expires"));
00190 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00191
00192
00193 #ifdef HTTP_TRANSFER_ENCODING
00194 buffers.push_back(create_cstr_asio_buffer("Transfer-Encoding"));
00195 buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
00196 buffers.push_back(create_cstr_asio_buffer("chunked"));
00197 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00198 #endif
00199
00200 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00201
00202 boost::asio::write(socket_, buffers);
00203 }
00204
00205 void connection::streamingWorkerThread( const std::string& topic,
00206 const ServerConfiguration& config)
00207 {
00208 FFMPEGEncoder image_encoder("WebEncoder", topic, config);
00209
00210 #ifdef HTTP_TRANSFER_ENCODING
00211 char hexSize[256];
00212 #endif
00213
00214 std::vector<uint8_t> header;
00215
00216 if (image_encoder.initEncoding(header) >= 0)
00217 {
00218
00219 std::vector<boost::asio::const_buffer> buffers;
00220 sendHTTPStreamingHeaders();
00221
00222 #ifdef HTTP_TRANSFER_ENCODING
00223 sprintf(hexSize, "%X", (unsigned int)header.size());
00224 buffers.push_back(create_cstr_asio_buffer(hexSize));
00225 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00226 #endif
00227
00228 buffers.push_back(boost::asio::buffer(header));
00229 #ifdef HTTP_TRANSFER_ENCODING
00230 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00231 #endif
00232
00233 try
00234 {
00235 boost::asio::write(socket_, buffers);
00236 ROS_DEBUG("Video header sent (%d bytes)", (int) header.size());
00237
00238 std::vector<uint8_t> packet;
00239
00240 while (do_streaming_)
00241 {
00242 buffers.clear();
00243 packet.clear();
00244
00245 image_encoder.getVideoPacket(packet);
00246 ROS_DEBUG("Video packet sent (%d bytes)", (int) packet.size());
00247
00248 #ifdef HTTP_TRANSFER_ENCODING
00249 sprintf(hexSize, "%X", (unsigned int)packet.size());
00250 buffers.push_back(create_cstr_asio_buffer(hexSize));
00251 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00252 #endif
00253
00254 buffers.push_back(boost::asio::buffer(packet));
00255 #ifdef HTTP_TRANSFER_ENCODING
00256 buffers.push_back(boost::asio::buffer(misc_strings::crlf));
00257 #endif
00258
00259 boost::asio::write(socket_, buffers);
00260 }
00261 }
00262 catch (const boost::system::system_error& err)
00263 {
00264 do_streaming_ = false;
00265 }
00266
00267 }
00268
00269 streaming_thread_.reset();
00270
00271 ROS_DEBUG("Video encoding thread endded");
00272 }
00273
00274 void connection::getImageTopics()
00275 {
00276 image_topics_.clear();
00277
00278 std::string image_message_type = ros::message_traits::datatype<sensor_msgs::Image>();
00279
00280 ros::master::V_TopicInfo topics;
00281 ros::master::getTopics( topics );
00282
00283
00284 ros::master::V_TopicInfo::iterator it;
00285 for( it = topics.begin(); it != topics.end(); ++it )
00286 {
00287 const ros::master::TopicInfo& topic = *it;
00288
00289
00290 if ( ( topic.datatype == image_message_type ) &&
00291 (topic.name.find("_raw")==std::string::npos ) )
00292 {
00293 image_topics_.push_back(topic.name);
00294 }
00295 }
00296 }
00297
00298
00299 void connection::generateImageTopicHTML()
00300 {
00301 reply_.content =
00302 "<html>"
00303 "<head><title>ROS Image Topic List</title></head>"
00304 "<body><h1>Available ROS Image Topics:</h1>";
00305
00306 BOOST_FOREACH( const std::string& topic_name, image_topics_ )
00307 {
00308 reply_.content += "<p><a href=";
00309 reply_.content += WEB_PATH;
00310 reply_.content += topic_name+">"+topic_name+"</a></p>";
00311 }
00312 reply_.content +=
00313
00314 "</body>"
00315 "</html>";
00316
00317 reply_.status = reply::ok;
00318 reply_.headers.resize(2);
00319 reply_.headers[0].name = "Content-Length";
00320 reply_.headers[0].value = boost::lexical_cast<std::string>(reply_.content.size());
00321 reply_.headers[1].name = "Content-Type";
00322 reply_.headers[1].value = "text/html";
00323 }
00324
00325 void connection::generateVideoStreamHTML(const std::string& image_topic,
00326 const ServerConfiguration& config)
00327 {
00328 reply_.content =
00329
00330 "<!DOCTYPE html>"
00331 "<html>"
00332 "<head>"
00333 "<meta charset=\"utf-8\">"
00334 "<title>ROS Topic: ";
00335
00336 reply_.content += image_topic;
00337
00338 reply_.content +=
00339 "</title>"
00340 "</head>"
00341 "<body>"
00342 "<div id=\"wrapper\">"
00343 "<h1>Video stream of ROS topic: ";
00344
00345 reply_.content += image_topic;
00346
00347 reply_.content +=
00348 "</h1>"
00349 "<div id=\"content\">"
00350
00351 "<div id=\"movie\">"
00352 "<video src=\"";
00353
00354
00355 reply_.content += STREAM_PATH;
00356 reply_.content += image_topic;
00357 reply_.content +=".webm?enc=";
00358 reply_.content +=config.codec_;
00359 reply_.content +="&bitrate=";
00360 reply_.content +=boost::lexical_cast<std::string>( config.bitrate_ );
00361 reply_.content +="&framerate=";
00362 reply_.content +=boost::lexical_cast<std::string>( config.framerate_ );
00363
00364 reply_.content +=
00365
00366 "\" autoplay=\"true\" preload=\"none\" > "
00367 "</video>"
00368 "</div>"
00369 "</div>"
00370 "</div>"
00371 "</body>"
00372 "</html>";
00373
00374 reply_.status = reply::ok;
00375 reply_.headers.resize(2);
00376 reply_.headers[0].name = "Content-Length";
00377 reply_.headers[0].value = boost::lexical_cast<std::string>(reply_.content.size());
00378 reply_.headers[1].name = "Content-Type";
00379 reply_.headers[1].value = "text/html";
00380 }
00381
00382 void connection::getStreamingParametersFromURL(const std::string url, ServerConfiguration& config)
00383 {
00384 std::vector<std::string> parameters;
00385 boost::split(parameters,url,boost::is_any_of("&"));
00386 BOOST_FOREACH( const std::string& p, parameters )
00387 {
00388 std::vector<std::string> setting;
00389 boost::split(setting,p,boost::is_any_of("="));
00390
00391 if (setting.size()==2)
00392 {
00393 try {
00394 if (!setting[0].compare("bitrate"))
00395 {
00396 config.bitrate_ = boost::lexical_cast<int>( setting[1] );
00397 } else
00398 if (!setting[0].compare("framerate"))
00399 {
00400 config.framerate_ = boost::lexical_cast<int>( setting[1] );
00401 } else
00402 if (!setting[0].compare("enc"))
00403 {
00404 config.codec_ = setting[1] ;
00405 } else
00406 if (!setting[0].compare("profile"))
00407 {
00408 config.profile_ = setting[1] ;
00409 } else
00410 if (!setting[0].compare("width"))
00411 {
00412 config.frame_width_ = boost::lexical_cast<int>( setting[1] );
00413 }
00414 else
00415 if (!setting[0].compare("height"))
00416 {
00417 config.frame_height_ = boost::lexical_cast<int>( setting[1] );
00418 }
00419 else
00420 if (!setting[0].compare("quality"))
00421 {
00422 config.quality_ = boost::lexical_cast<int>( setting[1] );
00423 }
00424 else
00425 if (!setting[0].compare("gop"))
00426 {
00427 config.gop_ = boost::lexical_cast<int>( setting[1] );
00428 } else
00429 {
00430 ROS_WARN_STREAM("Invalid configuration parameter in http request: "<<setting[1]);
00431 }
00432 } catch (boost::bad_lexical_cast& e) {}
00433 }
00434 }
00435
00436 }
00437
00438
00439 boost::asio::ip::tcp::socket& connection::socket()
00440 {
00441 return socket_;
00442 }
00443
00444 void connection::start()
00445 {
00446
00447 socket_.async_read_some(boost::asio::buffer(buffer_),
00448 strand_.wrap(
00449 boost::bind(&connection::handleRead, shared_from_this(),
00450 boost::asio::placeholders::error,
00451 boost::asio::placeholders::bytes_transferred)));
00452
00453 }
00454
00455 std::string connection::mimeExtensionToType(const std::string& extension)
00456 {
00457 for (mime_map* m = mime_mapping; m->extension; ++m)
00458 {
00459 if (m->extension == extension)
00460 {
00461 return m->mime_type;
00462 }
00463 }
00464
00465 return "text/plain";
00466 }
00467
00468 void connection::handleRead(const boost::system::error_code& e,
00469 std::size_t bytes_transferred)
00470 {
00471
00472 if (!e)
00473 {
00474 boost::tribool result;
00475 boost::tie(result, boost::tuples::ignore) = request_parser_.parse(
00476 request_, buffer_.data(), buffer_.data() + bytes_transferred);
00477
00478 const std::string remote_ip = boost::lexical_cast<std::string>(socket_.remote_endpoint());
00479
00480 if (result)
00481 {
00482 ROS_DEBUG("Http request received from %s: %s", remote_ip.c_str(), request_.uri.c_str());
00483
00484 std::string request_path;
00485
00486 getImageTopics();
00487
00488 if (!urlDecode(request_.uri, request_path))
00489 {
00490 reply_ = reply::stock_reply(reply::bad_request);
00491 } else
00492 if (request_path[request_path.size() - 1] == '/')
00493 {
00494
00495 generateImageTopicHTML();
00496 }
00497 else if (!request_path.empty() && request_path.find(STREAM_PATH) != std::string::npos)
00498 {
00499 std::string request_topic = request_path.substr(strlen(STREAM_PATH));
00500
00501 std::string image_topic = request_topic.substr(0, request_topic.find(".webm"));
00502 std::string parameter_req = request_topic.substr(request_topic.find("?")+1);
00503
00504
00505 bool stream_found = false;
00506 BOOST_FOREACH( const std::string& topic_name, image_topics_ )
00507 {
00508 if (!image_topic.compare(topic_name))
00509 stream_found = true;
00510 }
00511
00512 if (stream_found)
00513 {
00514 ServerConfiguration config = server_conf_;
00515
00516
00517 getStreamingParametersFromURL(parameter_req, config);
00518
00519
00520 do_streaming_ = true;
00521 streaming_thread_ = boost::shared_ptr<boost::thread>(
00522 new boost::thread( boost::bind(&connection::streamingWorkerThread, shared_from_this(), image_topic, config ) ) );
00523
00524 ROS_INFO("Starting encoder for topic %s (codec: %s, bitrate: %d, framerate: %d)",
00525 image_topic.c_str(),
00526 config.codec_.c_str(),
00527 config.bitrate_,
00528 config.framerate_);
00529
00530 return;
00531 }
00532 }
00533 else if (!request_path.empty() && request_path.find(WEB_PATH) != std::string::npos)
00534 {
00535 std::string request_topic = request_path.substr(strlen(WEB_PATH));
00536
00537 std::string image_topic = request_topic.substr(0, request_topic.find("?"));
00538
00539 bool stream_found = false;
00540 BOOST_FOREACH( const std::string& topic_name, image_topics_ )
00541 {
00542 if (!image_topic.compare(topic_name))
00543 stream_found = true;
00544 }
00545
00546 if (stream_found)
00547 {
00548 ROS_INFO("Requesting www for %s", image_topic.c_str());
00549
00550
00551 generateVideoStreamHTML(image_topic, server_conf_);
00552
00553 } else
00554 {
00555 reply_ = reply::stock_reply(reply::not_found);
00556 }
00557 } else
00558 {
00559
00560 if (server_conf_.www_file_server_)
00561 {
00562 std::size_t last_slash_pos = request_path.find_last_of("/");
00563 std::size_t last_dot_pos = request_path.find_last_of(".");
00564 std::string extension;
00565 if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos)
00566 {
00567 extension = request_path.substr(last_dot_pos + 1);
00568 }
00569
00570
00571 std::string full_path = server_conf_.wwwroot_ + request_path;
00572 std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
00573 if (!is)
00574 {
00575 reply_ = reply::stock_reply(reply::not_found);
00576 ROS_INFO("Http request from client %s: %s - file not found", remote_ip.c_str(), full_path.c_str());
00577 return;
00578 }
00579
00580
00581 reply_.status = reply::ok;
00582 char buf[2048];
00583 while (is.read(buf, sizeof(buf)).gcount() > 0)
00584 reply_.content.append(buf, is.gcount());
00585 reply_.headers.resize(2);
00586 reply_.headers[0].name = "Content-Length";
00587 reply_.headers[0].value = boost::lexical_cast < std::string > (reply_.content.size());
00588 reply_.headers[1].name = "Content-Type";
00589 reply_.headers[1].value = mimeExtensionToType(extension);
00590 } else
00591 {
00592 reply_ = reply::stock_reply(reply::forbidden);
00593 boost::asio::async_write(socket_, reply_.to_buffers(),
00594 strand_.wrap(
00595 boost::bind(&connection::handleWrite, shared_from_this(),
00596 boost::asio::placeholders::error)));
00597 }
00598 }
00599
00600
00601 boost::asio::async_write(socket_, reply_.to_buffers(),
00602 strand_.wrap(
00603 boost::bind(&connection::handleWrite, shared_from_this(),
00604 boost::asio::placeholders::error)));
00605 }
00606 else if (!result)
00607 {
00608 reply_ = reply::stock_reply(reply::bad_request);
00609 boost::asio::async_write(socket_, reply_.to_buffers(),
00610 strand_.wrap(
00611 boost::bind(&connection::handleWrite, shared_from_this(),
00612 boost::asio::placeholders::error)));
00613 }
00614 else
00615 {
00616 socket_.async_read_some(boost::asio::buffer(buffer_),
00617 strand_.wrap(
00618 boost::bind(&connection::handleRead, shared_from_this(),
00619 boost::asio::placeholders::error,
00620 boost::asio::placeholders::bytes_transferred)));
00621 }
00622 } else
00623 {
00624 ROS_INFO("Http read error: %s", e.message().c_str());
00625 }
00626
00627
00628
00629
00630
00631 }
00632
00633
00634 void connection::handleWrite(const boost::system::error_code& e)
00635 {
00636 if (!e)
00637 {
00638
00639 boost::system::error_code ignored_ec;
00640 socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
00641 }
00642
00643
00644
00645
00646
00647 }
00648
00649 bool connection::urlDecode(const std::string& in, std::string& out)
00650 {
00651 out.clear();
00652 out.reserve(in.size());
00653 for (std::size_t i = 0; i < in.size(); ++i)
00654 {
00655 if (in[i] == '%')
00656 {
00657 if (i + 3 <= in.size())
00658 {
00659 int value = 0;
00660 std::istringstream is(in.substr(i + 1, 2));
00661 if (is >> std::hex >> value)
00662 {
00663 out += static_cast<char>(value);
00664 i += 2;
00665 }
00666 else
00667 {
00668 return false;
00669 }
00670 }
00671 else
00672 {
00673 return false;
00674 }
00675 }
00676 else if (in[i] == '+')
00677 {
00678 out += ' ';
00679 }
00680 else
00681 {
00682 out += in[i];
00683 }
00684 }
00685 return true;
00686 }
00687
00688 }