1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/tools/flip_server/spdy_interface.h"
10 #include "net/spdy/spdy_framer.h"
11 #include "net/spdy/spdy_protocol.h"
12 #include "net/tools/flip_server/constants.h"
13 #include "net/tools/flip_server/flip_config.h"
14 #include "net/tools/flip_server/http_interface.h"
15 #include "net/tools/flip_server/spdy_util.h"
16 #include "net/tools/flip_server/url_utilities.h"
21 std::string
SpdySM::forward_ip_header_
;
23 class SpdyFrameDataFrame
: public DataFrame
{
25 explicit SpdyFrameDataFrame(SpdyFrame
* spdy_frame
) : frame(spdy_frame
) {
26 data
= spdy_frame
->data();
27 size
= spdy_frame
->size();
30 ~SpdyFrameDataFrame() override
{ delete frame
; }
32 const SpdyFrame
* frame
;
35 SpdySM::SpdySM(SMConnection
* connection
,
36 SMInterface
* sm_http_interface
,
37 EpollServer
* epoll_server
,
38 MemoryCache
* memory_cache
,
39 FlipAcceptor
* acceptor
,
40 SpdyMajorVersion spdy_version
)
41 : buffered_spdy_framer_(new BufferedSpdyFramer(spdy_version
, true)),
42 valid_spdy_session_(false),
43 connection_(connection
),
44 client_output_list_(connection
->output_list()),
45 client_output_ordering_(connection
),
46 next_outgoing_stream_id_(2),
47 epoll_server_(epoll_server
),
49 memory_cache_(memory_cache
),
50 close_on_error_(false) {
51 buffered_spdy_framer_
->set_visitor(this);
56 void SpdySM::InitSMConnection(SMConnectionPoolInterface
* connection_pool
,
57 SMInterface
* sm_interface
,
58 EpollServer
* epoll_server
,
60 std::string server_ip
,
61 std::string server_port
,
62 std::string remote_ip
,
64 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: Initializing server connection.";
65 connection_
->InitSMConnection(connection_pool
,
75 SMInterface
* SpdySM::NewConnectionInterface() {
76 SMConnection
* server_connection
=
77 SMConnection::NewSMConnection(epoll_server_
,
82 if (server_connection
== NULL
) {
83 LOG(ERROR
) << "SpdySM: Could not create server connection";
86 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: Creating new HTTP interface";
87 SMInterface
* sm_http_interface
=
88 new HttpSM(server_connection
, this, memory_cache_
, acceptor_
);
89 return sm_http_interface
;
92 SMInterface
* SpdySM::FindOrMakeNewSMConnectionInterface(
93 const std::string
& server_ip
,
94 const std::string
& server_port
) {
95 SMInterface
* sm_http_interface
;
97 if (unused_server_interface_list
.empty()) {
98 sm_http_interface
= NewConnectionInterface();
99 server_idx
= server_interface_list
.size();
100 server_interface_list
.push_back(sm_http_interface
);
101 VLOG(2) << ACCEPTOR_CLIENT_IDENT
102 << "SpdySM: Making new server connection on index: " << server_idx
;
104 server_idx
= unused_server_interface_list
.back();
105 unused_server_interface_list
.pop_back();
106 sm_http_interface
= server_interface_list
.at(server_idx
);
107 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: Reusing connection on "
108 << "index: " << server_idx
;
111 sm_http_interface
->InitSMInterface(this, server_idx
);
112 sm_http_interface
->InitSMConnection(NULL
,
121 return sm_http_interface
;
124 int SpdySM::SpdyHandleNewStream(SpdyStreamId stream_id
,
125 SpdyPriority priority
,
126 const SpdyHeaderBlock
& headers
,
127 std::string
& http_data
,
128 bool* is_https_scheme
) {
129 *is_https_scheme
= false;
130 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: OnSyn(" << stream_id
<< ")";
131 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: # headers: " << headers
.size();
133 SpdyHeaderBlock::const_iterator method
= headers
.end();
134 SpdyHeaderBlock::const_iterator host
= headers
.end();
135 SpdyHeaderBlock::const_iterator path
= headers
.end();
136 SpdyHeaderBlock::const_iterator scheme
= headers
.end();
137 SpdyHeaderBlock::const_iterator version
= headers
.end();
138 SpdyHeaderBlock::const_iterator url
= headers
.end();
140 std::string path_string
, host_string
, version_string
;
142 if (spdy_version() == SPDY2
) {
143 url
= headers
.find("url");
144 method
= headers
.find("method");
145 version
= headers
.find("version");
146 scheme
= headers
.find("scheme");
147 if (url
== headers
.end() || method
== headers
.end() ||
148 version
== headers
.end() || scheme
== headers
.end()) {
149 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: A mandatory header is "
150 << "missing. Not creating stream";
153 // url->second here only ever seems to contain just the path. When this
154 // path contains a query string with a http:// in one of its values,
155 // UrlUtilities::GetUrlPath will fail and always return a / breaking
156 // the request. GetUrlPath assumes the absolute URL is being passed in.
157 path_string
= UrlUtilities::GetUrlPath(url
->second
);
158 host_string
= UrlUtilities::GetUrlHost(url
->second
);
159 version_string
= version
->second
;
161 method
= headers
.find(":method");
162 host
= headers
.find(":host");
163 path
= headers
.find(":path");
164 scheme
= headers
.find(":scheme");
165 if (method
== headers
.end() || host
== headers
.end() ||
166 path
== headers
.end() || scheme
== headers
.end()) {
167 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: A mandatory header is "
168 << "missing. Not creating stream";
171 host_string
= host
->second
;
172 path_string
= path
->second
;
173 version_string
= "HTTP/1.1";
176 if (scheme
->second
.compare("https") == 0) {
177 *is_https_scheme
= true;
180 if (acceptor_
->flip_handler_type_
== FLIP_HANDLER_SPDY_SERVER
) {
181 VLOG(1) << ACCEPTOR_CLIENT_IDENT
<< "Request: " << method
->second
182 << " " << path_string
;
183 std::string filename
= EncodeURL(path_string
,
186 NewStream(stream_id
, priority
, filename
);
189 method
->second
+ " " + path_string
+ " " + version_string
+ "\r\n";
190 VLOG(1) << ACCEPTOR_CLIENT_IDENT
<< "Request: " << method
->second
<< " "
191 << path_string
<< " " << version_string
;
192 http_data
+= "Host: " + (*is_https_scheme
?
193 acceptor_
->https_server_ip_
:
194 acceptor_
->http_server_ip_
) + "\r\n";
195 for (SpdyHeaderBlock::const_iterator i
= headers
.begin();
196 i
!= headers
.end(); ++i
) {
197 if ((i
->first
.size() > 0 && i
->first
[0] == ':') ||
198 i
->first
== "host" ||
207 http_data
+= i
->first
+ ": " + i
->second
+ "\r\n";
208 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< i
->first
.c_str() << ":"
209 << i
->second
.c_str();
212 if (forward_ip_header_
.length()) {
213 // X-Client-Cluster-IP header
214 http_data
+= forward_ip_header_
+ ": " +
215 connection_
->client_ip() + "\r\n";
220 VLOG(3) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: HTTP Request:\n" << http_data
;
224 void SpdySM::OnStreamFrameData(SpdyStreamId stream_id
,
228 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: StreamData(" << stream_id
229 << ", [" << len
<< "])";
230 StreamToSmif::iterator it
= stream_to_smif_
.find(stream_id
);
231 if (it
== stream_to_smif_
.end()) {
232 VLOG(2) << "Dropping frame from unknown stream " << stream_id
;
233 if (!valid_spdy_session_
)
234 close_on_error_
= true;
238 SMInterface
* interface
= it
->second
;
239 if (acceptor_
->flip_handler_type_
== FLIP_HANDLER_PROXY
)
240 interface
->ProcessWriteInput(data
, len
);
243 void SpdySM::OnStreamPadding(SpdyStreamId stream_id
, size_t len
) {
244 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: StreamPadding(" << stream_id
245 << ", [" << len
<< "])";
248 void SpdySM::OnSynStream(SpdyStreamId stream_id
,
249 SpdyStreamId associated_stream_id
,
250 SpdyPriority priority
,
253 const SpdyHeaderBlock
& headers
) {
254 std::string http_data
;
255 bool is_https_scheme
;
256 int ret
= SpdyHandleNewStream(
257 stream_id
, priority
, headers
, http_data
, &is_https_scheme
);
259 LOG(ERROR
) << "SpdySM: Could not convert spdy into http.";
262 // We've seen a valid looking SYN_STREAM, consider this to have
263 // been a real spdy session.
264 valid_spdy_session_
= true;
266 if (acceptor_
->flip_handler_type_
== FLIP_HANDLER_PROXY
) {
267 std::string server_ip
;
268 std::string server_port
;
269 if (is_https_scheme
) {
270 server_ip
= acceptor_
->https_server_ip_
;
271 server_port
= acceptor_
->https_server_port_
;
273 server_ip
= acceptor_
->http_server_ip_
;
274 server_port
= acceptor_
->http_server_port_
;
276 SMInterface
* sm_http_interface
=
277 FindOrMakeNewSMConnectionInterface(server_ip
, server_port
);
278 stream_to_smif_
[stream_id
] = sm_http_interface
;
279 sm_http_interface
->SetStreamID(stream_id
);
280 sm_http_interface
->ProcessWriteInput(http_data
.c_str(), http_data
.size());
284 void SpdySM::OnSynReply(SpdyStreamId stream_id
,
286 const SpdyHeaderBlock
& headers
) {
287 // TODO(willchan): if there is an error parsing headers, we
288 // should send a RST_STREAM.
289 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: OnSynReply(" << stream_id
<< ")";
292 void SpdySM::OnHeaders(SpdyStreamId stream_id
,
294 SpdyPriority priority
,
295 SpdyStreamId parent_stream_id
,
298 const SpdyHeaderBlock
& headers
) {
299 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: OnHeaders(" << stream_id
<< ")";
302 void SpdySM::OnRstStream(SpdyStreamId stream_id
, SpdyRstStreamStatus status
) {
303 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: OnRstStream(" << stream_id
305 client_output_ordering_
.RemoveStreamId(stream_id
);
308 bool SpdySM::OnUnknownFrame(SpdyStreamId stream_id
, int frame_type
) {
312 size_t SpdySM::ProcessReadInput(const char* data
, size_t len
) {
313 DCHECK(buffered_spdy_framer_
);
314 return buffered_spdy_framer_
->ProcessInput(data
, len
);
317 size_t SpdySM::ProcessWriteInput(const char* data
, size_t len
) { return 0; }
319 bool SpdySM::MessageFullyRead() const {
320 DCHECK(buffered_spdy_framer_
);
321 return buffered_spdy_framer_
->MessageFullyRead();
324 bool SpdySM::Error() const {
325 DCHECK(buffered_spdy_framer_
);
326 return close_on_error_
|| buffered_spdy_framer_
->HasError();
329 const char* SpdySM::ErrorAsString() const {
331 DCHECK(buffered_spdy_framer_
);
332 return SpdyFramer::ErrorCodeToString(buffered_spdy_framer_
->error_code());
335 void SpdySM::ResetForNewInterface(int32 server_idx
) {
336 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: Reset for new interface: "
337 << "server_idx: " << server_idx
;
338 unused_server_interface_list
.push_back(server_idx
);
341 void SpdySM::ResetForNewConnection() {
342 // seq_num is not cleared, intentionally.
343 buffered_spdy_framer_
.reset();
344 valid_spdy_session_
= false;
345 client_output_ordering_
.Reset();
346 next_outgoing_stream_id_
= 2;
349 // Send a settings frame
350 int SpdySM::PostAcceptHook() {
351 // We should have buffered_spdy_framer_ set after reuse
352 DCHECK(buffered_spdy_framer_
);
353 SettingsMap settings
;
354 settings
[SETTINGS_MAX_CONCURRENT_STREAMS
] =
355 SettingsFlagsAndValue(SETTINGS_FLAG_NONE
, 100);
356 SpdyFrame
* settings_frame
= buffered_spdy_framer_
->CreateSettings(settings
);
358 VLOG(1) << ACCEPTOR_CLIENT_IDENT
<< "Sending Settings Frame";
359 EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame
));
363 void SpdySM::NewStream(uint32 stream_id
,
365 const std::string
& filename
) {
367 mci
.stream_id
= stream_id
;
368 mci
.priority
= priority
;
369 // TODO(yhirano): The program will crash when
370 // acceptor_->flip_handler_type_ != FLIP_HANDLER_SPDY_SERVER.
371 // It should be fixed or an assertion should be placed.
372 if (acceptor_
->flip_handler_type_
== FLIP_HANDLER_SPDY_SERVER
) {
373 if (!memory_cache_
->AssignFileData(filename
, &mci
)) {
374 // error creating new stream.
375 VLOG(1) << ACCEPTOR_CLIENT_IDENT
<< "Sending ErrorNotFound";
376 SendErrorNotFound(stream_id
);
378 AddToOutputOrder(mci
);
381 AddToOutputOrder(mci
);
385 void SpdySM::AddToOutputOrder(const MemCacheIter
& mci
) {
386 client_output_ordering_
.AddToOutputOrder(mci
);
389 void SpdySM::SendEOF(uint32 stream_id
) { SendEOFImpl(stream_id
); }
391 void SpdySM::SendErrorNotFound(uint32 stream_id
) {
392 SendErrorNotFoundImpl(stream_id
);
395 size_t SpdySM::SendSynStream(uint32 stream_id
, const BalsaHeaders
& headers
) {
396 return SendSynStreamImpl(stream_id
, headers
);
399 size_t SpdySM::SendSynReply(uint32 stream_id
, const BalsaHeaders
& headers
) {
400 return SendSynReplyImpl(stream_id
, headers
);
403 void SpdySM::SendDataFrame(uint32 stream_id
,
408 SpdyDataFlags spdy_flags
= static_cast<SpdyDataFlags
>(flags
);
409 SendDataFrameImpl(stream_id
, data
, len
, spdy_flags
, compress
);
412 void SpdySM::SendEOFImpl(uint32 stream_id
) {
413 SendDataFrame(stream_id
, NULL
, 0, DATA_FLAG_FIN
, false);
414 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: Sending EOF: " << stream_id
;
415 KillStream(stream_id
);
416 stream_to_smif_
.erase(stream_id
);
419 void SpdySM::SendErrorNotFoundImpl(uint32 stream_id
) {
420 BalsaHeaders my_headers
;
421 my_headers
.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
422 SendSynReplyImpl(stream_id
, my_headers
);
423 SendDataFrame(stream_id
, "wtf?", 4, DATA_FLAG_FIN
, false);
424 client_output_ordering_
.RemoveStreamId(stream_id
);
427 void SpdySM::KillStream(uint32 stream_id
) {
428 client_output_ordering_
.RemoveStreamId(stream_id
);
431 void SpdySM::CopyHeaders(SpdyHeaderBlock
& dest
, const BalsaHeaders
& headers
) {
432 for (BalsaHeaders::const_header_lines_iterator hi
=
433 headers
.header_lines_begin();
434 hi
!= headers
.header_lines_end();
436 // It is illegal to send SPDY headers with empty value or header
438 if (!hi
->first
.length() || !hi
->second
.length())
441 // Key must be all lower case in SPDY headers.
442 std::string key
= hi
->first
.as_string();
443 std::transform(key
.begin(), key
.end(), key
.begin(), ::tolower
);
444 SpdyHeaderBlock::iterator fhi
= dest
.find(key
);
445 if (fhi
== dest
.end()) {
446 dest
[key
] = hi
->second
.as_string();
448 dest
[key
] = (std::string(fhi
->second
.data(), fhi
->second
.size()) + "\0" +
449 std::string(hi
->second
.data(), hi
->second
.size()));
453 // These headers have no value
454 dest
.erase("X-Associated-Content"); // TODO(mbelshe): case-sensitive
455 dest
.erase("X-Original-Url"); // TODO(mbelshe): case-sensitive
458 size_t SpdySM::SendSynStreamImpl(uint32 stream_id
,
459 const BalsaHeaders
& headers
) {
460 SpdyHeaderBlock block
;
461 CopyHeaders(block
, headers
);
462 if (spdy_version() == SPDY2
) {
463 block
["method"] = headers
.request_method().as_string();
464 if (!headers
.HasHeader("version"))
465 block
["version"] = headers
.request_version().as_string();
466 if (headers
.HasHeader("X-Original-Url")) {
467 std::string original_url
=
468 headers
.GetHeader("X-Original-Url").as_string();
469 block
["url"] = UrlUtilities::GetUrlPath(original_url
);
471 block
["url"] = headers
.request_uri().as_string();
474 block
[":method"] = headers
.request_method().as_string();
475 block
[":version"] = headers
.request_version().as_string();
476 if (headers
.HasHeader("X-Original-Url")) {
477 std::string original_url
=
478 headers
.GetHeader("X-Original-Url").as_string();
479 block
[":path"] = UrlUtilities::GetUrlPath(original_url
);
480 block
[":host"] = UrlUtilities::GetUrlPath(original_url
);
482 block
[":path"] = headers
.request_uri().as_string();
483 if (block
.find("host") != block
.end()) {
484 block
[":host"] = headers
.GetHeader("Host").as_string();
490 DCHECK(buffered_spdy_framer_
);
491 SpdyFrame
* fsrcf
= buffered_spdy_framer_
->CreateSynStream(
492 stream_id
, 0, 0, CONTROL_FLAG_NONE
, &block
);
493 size_t df_size
= fsrcf
->size();
494 EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf
));
496 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: Sending SynStreamheader "
501 size_t SpdySM::SendSynReplyImpl(uint32 stream_id
, const BalsaHeaders
& headers
) {
502 SpdyHeaderBlock block
;
503 CopyHeaders(block
, headers
);
504 if (spdy_version() == SPDY2
) {
505 block
["status"] = headers
.response_code().as_string() + " " +
506 headers
.response_reason_phrase().as_string();
507 block
["version"] = headers
.response_version().as_string();
509 block
[":status"] = headers
.response_code().as_string() + " " +
510 headers
.response_reason_phrase().as_string();
511 block
[":version"] = headers
.response_version().as_string();
514 DCHECK(buffered_spdy_framer_
);
515 SpdyFrame
* fsrcf
= buffered_spdy_framer_
->CreateSynReply(
516 stream_id
, CONTROL_FLAG_NONE
, &block
);
517 size_t df_size
= fsrcf
->size();
518 EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf
));
520 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: Sending SynReplyheader "
525 void SpdySM::SendDataFrameImpl(uint32 stream_id
,
530 DCHECK(buffered_spdy_framer_
);
531 // TODO(mbelshe): We can't compress here - before going into the
532 // priority queue. Compression needs to be done
533 // with late binding.
536 buffered_spdy_framer_
->CreateDataFrame(stream_id
, data
, len
, flags
);
537 EnqueueDataFrame(new SpdyFrameDataFrame(fdf
));
541 // Chop data frames into chunks so that one stream can't monopolize the
544 int64 size
= std::min(len
, static_cast<int64
>(kSpdySegmentSize
));
545 SpdyDataFlags chunk_flags
= flags
;
547 // If we chunked this block, and the FIN flag was set, there is more
548 // data coming. So, remove the flag.
549 if ((size
< len
) && (flags
& DATA_FLAG_FIN
))
550 chunk_flags
= static_cast<SpdyDataFlags
>(chunk_flags
& ~DATA_FLAG_FIN
);
552 SpdyFrame
* fdf
= buffered_spdy_framer_
->CreateDataFrame(
553 stream_id
, data
, size
, chunk_flags
);
554 EnqueueDataFrame(new SpdyFrameDataFrame(fdf
));
556 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: Sending data frame "
557 << stream_id
<< " [" << size
<< "] shrunk to "
558 << (fdf
->size() - kSpdyOverhead
) << ", flags=" << flags
;
565 void SpdySM::EnqueueDataFrame(DataFrame
* df
) {
566 connection_
->EnqueueDataFrame(df
);
569 void SpdySM::GetOutput() {
570 while (client_output_list_
->size() < 2) {
571 MemCacheIter
* mci
= client_output_ordering_
.GetIter();
573 VLOG(2) << ACCEPTOR_CLIENT_IDENT
574 << "SpdySM: GetOutput: nothing to output!?";
577 if (!mci
->transformed_header
) {
578 mci
->transformed_header
= true;
579 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: GetOutput transformed "
580 << "header stream_id: [" << mci
->stream_id
<< "]";
581 if ((mci
->stream_id
% 2) == 0) {
582 // this is a server initiated stream.
583 // Ideally, we'd do a 'syn-push' here, instead of a syn-reply.
584 BalsaHeaders headers
;
585 headers
.CopyFrom(*(mci
->file_data
->headers()));
586 headers
.ReplaceOrAppendHeader("status", "200");
587 headers
.ReplaceOrAppendHeader("version", "http/1.1");
588 headers
.SetRequestFirstlineFromStringPieces(
589 "PUSH", mci
->file_data
->filename(), "");
590 mci
->bytes_sent
= SendSynStream(mci
->stream_id
, headers
);
592 BalsaHeaders headers
;
593 headers
.CopyFrom(*(mci
->file_data
->headers()));
594 mci
->bytes_sent
= SendSynReply(mci
->stream_id
, headers
);
598 if (mci
->body_bytes_consumed
>= mci
->file_data
->body().size()) {
599 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: GetOutput "
600 << "remove_stream_id: [" << mci
->stream_id
<< "]";
601 SendEOF(mci
->stream_id
);
604 size_t num_to_write
=
605 mci
->file_data
->body().size() - mci
->body_bytes_consumed
;
606 if (num_to_write
> mci
->max_segment_size
)
607 num_to_write
= mci
->max_segment_size
;
609 bool should_compress
= false;
610 if (!mci
->file_data
->headers()->HasHeader("content-encoding")) {
611 if (mci
->file_data
->headers()->HasHeader("content-type")) {
612 std::string content_type
=
613 mci
->file_data
->headers()->GetHeader("content-type").as_string();
614 if (content_type
.find("image") == content_type
.npos
)
615 should_compress
= true;
619 SendDataFrame(mci
->stream_id
,
620 mci
->file_data
->body().data() + mci
->body_bytes_consumed
,
624 VLOG(2) << ACCEPTOR_CLIENT_IDENT
<< "SpdySM: GetOutput SendDataFrame["
625 << mci
->stream_id
<< "]: " << num_to_write
;
626 mci
->body_bytes_consumed
+= num_to_write
;
627 mci
->bytes_sent
+= num_to_write
;
631 void SpdySM::CreateFramer(SpdyMajorVersion spdy_version
) {
632 DCHECK(!buffered_spdy_framer_
);
633 buffered_spdy_framer_
.reset(new BufferedSpdyFramer(spdy_version
, true));
634 buffered_spdy_framer_
->set_visitor(this);