1use std::{
2 collections::{HashMap, VecDeque},
3 fmt,
4 rc::Rc,
5};
6
7use kawa::{
8 AsBuffer, Block, BodySize, Buffer, Chunk, Kawa, Kind, Pair, ParsingPhase, ParsingPhaseMarker,
9 StatusLine, Store, h1::NoCallbacks,
10};
11use sozu_command::proto::command::CustomHttpAnswers;
12
13use crate::{protocol::http::DefaultAnswer, sozu_command::state::ClusterId};
14
15#[derive(Clone)]
16pub struct SharedBuffer(Rc<[u8]>);
17
18impl AsBuffer for SharedBuffer {
19 fn as_buffer(&self) -> &[u8] {
20 &self.0
21 }
22
23 fn as_mut_buffer(&mut self) -> &mut [u8] {
24 panic!()
25 }
26}
27
28pub type DefaultAnswerStream = Kawa<SharedBuffer>;
29
30#[derive(thiserror::Error, Debug)]
31pub enum TemplateError {
32 #[error("invalid template type: request was found, expected response")]
33 InvalidType,
34 #[error("template seems invalid or incomplete: {0:?}")]
35 InvalidTemplate(ParsingPhase),
36 #[error("unexpected status code: {0}")]
37 InvalidStatusCode(u16),
38 #[error("streaming is not supported in templates")]
39 UnsupportedStreaming,
40 #[error("template variable {0} is not allowed in headers")]
41 NotAllowedInHeader(&'static str),
42 #[error("template variable {0} is not allowed in body")]
43 NotAllowedInBody(&'static str),
44 #[error("template variable {0} can only be used once")]
45 AlreadyConsumed(&'static str),
46}
47
48#[derive(Clone, Copy, Debug)]
49pub struct TemplateVariable {
50 name: &'static str,
51 valid_in_body: bool,
52 valid_in_header: bool,
53 typ: ReplacementType,
54}
55
56#[derive(Clone, Copy, Debug)]
57pub enum ReplacementType {
58 Variable(usize),
59 VariableOnce(usize),
60 ContentLength,
61}
62
63#[derive(Clone, Copy, Debug)]
64pub struct Replacement {
65 block_index: usize,
66 typ: ReplacementType,
67}
68
69pub struct Template {
71 kawa: DefaultAnswerStream,
72 body_replacements: Vec<Replacement>,
73 header_replacements: Vec<Replacement>,
74 body_size: usize,
76}
77
78impl fmt::Debug for Template {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 f.debug_struct("Template")
81 .field("body_replacements", &self.body_replacements)
82 .field("header_replacements", &self.header_replacements)
83 .field("body_size", &self.body_size)
84 .finish()
85 }
86}
87
88impl Template {
89 fn new(
91 status: u16,
92 answer: String,
93 variables: &[TemplateVariable],
94 ) -> Result<Self, TemplateError> {
95 let mut i = 0;
96 let mut j = 0;
97 let variables = variables
98 .iter()
99 .map(|v| match v.typ {
100 ReplacementType::Variable(_) => {
101 i += 1;
102 TemplateVariable {
103 typ: ReplacementType::Variable(i - 1),
104 ..*v
105 }
106 }
107 ReplacementType::VariableOnce(_) => {
108 j += 1;
109 TemplateVariable {
110 typ: ReplacementType::VariableOnce(j - 1),
111 ..*v
112 }
113 }
114 ReplacementType::ContentLength => *v,
115 })
116 .collect::<Vec<_>>();
117 let answer = answer
118 .replace("\r\n", "\n")
119 .replace('\n', "\r\n")
120 .into_bytes();
121
122 let len = answer.len();
123 let mut kawa = Kawa::new(Kind::Response, Buffer::new(SharedBuffer(Rc::from(answer))));
124 kawa.storage.end = len;
125 kawa::h1::parse(&mut kawa, &mut NoCallbacks);
126 if !kawa.is_main_phase() {
127 return Err(TemplateError::InvalidTemplate(kawa.parsing_phase));
128 }
129 if let StatusLine::Response { code, .. } = kawa.detached.status_line {
130 if code != status {
131 return Err(TemplateError::InvalidStatusCode(code));
132 }
133 } else {
134 return Err(TemplateError::InvalidType);
135 }
136 let buf = kawa.storage.buffer();
137 let mut blocks = VecDeque::new();
138 let mut header_replacements = Vec::new();
139 let mut body_replacements = Vec::new();
140 let mut body_size = 0;
141 let mut used_once = Vec::new();
142 for mut block in kawa.blocks.into_iter() {
143 match &mut block {
144 Block::ChunkHeader(_) => return Err(TemplateError::UnsupportedStreaming),
145 Block::StatusLine | Block::Cookies | Block::Flags(_) => {
146 blocks.push_back(block);
147 }
148 Block::Header(Pair { key, val }) => {
149 let val_data = val.data(buf);
150 let key_data = key.data(buf);
151 if let Some(b'%') = val_data.first() {
152 for variable in &variables {
153 if &val_data[1..] == variable.name.as_bytes() {
154 if !variable.valid_in_header {
155 return Err(TemplateError::NotAllowedInHeader(variable.name));
156 }
157 *val = Store::Static(b"PLACEHOLDER");
158 match variable.typ {
159 ReplacementType::Variable(_) => {}
160 ReplacementType::VariableOnce(var_index) => {
161 if used_once.contains(&var_index) {
162 return Err(TemplateError::AlreadyConsumed(
163 variable.name,
164 ));
165 }
166 used_once.push(var_index);
167 }
168 ReplacementType::ContentLength => {
169 if let Some(b'%') = key_data.first() {
170 *key = Store::new_slice(buf, &key_data[1..]);
171 }
172 }
173 }
174 header_replacements.push(Replacement {
175 block_index: blocks.len(),
176 typ: variable.typ,
177 });
178 break;
179 }
180 }
181 }
182 blocks.push_back(block);
183 }
184 Block::Chunk(Chunk { data }) => {
185 let data = data.data(buf);
186 body_size += data.len();
187 let mut start = 0;
188 let mut i = 0;
189 while i < data.len() {
190 if data[i] == b'%' {
191 for variable in &variables {
192 if data[i + 1..].starts_with(variable.name.as_bytes()) {
193 if !variable.valid_in_body {
194 return Err(TemplateError::NotAllowedInBody(variable.name));
195 }
196 if start < i {
197 blocks.push_back(Block::Chunk(Chunk {
198 data: Store::new_slice(buf, &data[start..i]),
199 }));
200 }
201 body_size -= variable.name.len() + 1;
202 start = i + variable.name.len() + 1;
203 i += variable.name.len();
204 match variable.typ {
205 ReplacementType::Variable(_) => {}
206 ReplacementType::ContentLength => {}
207 ReplacementType::VariableOnce(var_index) => {
208 if used_once.contains(&var_index) {
209 return Err(TemplateError::AlreadyConsumed(
210 variable.name,
211 ));
212 }
213 used_once.push(var_index);
214 }
215 }
216 body_replacements.push(Replacement {
217 block_index: blocks.len(),
218 typ: variable.typ,
219 });
220 blocks.push_back(Block::Chunk(Chunk {
221 data: Store::Static(b"PLACEHOLDER"),
222 }));
223 break;
224 }
225 }
226 }
227 i += 1;
228 }
229 if start < data.len() {
230 blocks.push_back(Block::Chunk(Chunk {
231 data: Store::new_slice(buf, &data[start..]),
232 }));
233 }
234 }
235 }
236 }
237 kawa.blocks = blocks;
238 Ok(Self {
239 kawa,
240 body_replacements,
241 header_replacements,
242 body_size,
243 })
244 }
245
246 fn fill(&self, variables: &[Vec<u8>], variables_once: &mut [Vec<u8>]) -> DefaultAnswerStream {
247 let mut blocks = self.kawa.blocks.clone();
248 let mut body_size = self.body_size;
249 for replacement in &self.body_replacements {
250 match replacement.typ {
251 ReplacementType::Variable(var_index) => {
252 let variable = &variables[var_index];
253 body_size += variable.len();
254 blocks[replacement.block_index] = Block::Chunk(Chunk {
255 data: Store::from_slice(variable),
256 })
257 }
258 ReplacementType::VariableOnce(var_index) => {
259 let variable = std::mem::take(&mut variables_once[var_index]);
260 body_size += variable.len();
261 blocks[replacement.block_index] = Block::Chunk(Chunk {
262 data: Store::from_vec(variable),
263 })
264 }
265 ReplacementType::ContentLength => unreachable!(),
266 }
267 }
268 for replacement in &self.header_replacements {
269 if let Block::Header(pair) = &mut blocks[replacement.block_index] {
270 match replacement.typ {
271 ReplacementType::Variable(var_index) => {
272 pair.val = Store::from_slice(&variables[var_index]);
273 }
274 ReplacementType::VariableOnce(var_index) => {
275 let variable = std::mem::take(&mut variables_once[var_index]);
276 pair.val = Store::from_vec(variable);
277 }
278 ReplacementType::ContentLength => {
279 pair.val = Store::from_string(body_size.to_string())
280 }
281 }
282 }
283 }
284 Kawa {
285 storage: Buffer::new(self.kawa.storage.buffer.clone()),
286 blocks,
287 out: Default::default(),
288 detached: self.kawa.detached.clone(),
289 kind: Kind::Response,
290 expects: 0,
291 parsing_phase: ParsingPhase::Terminated,
292 body_size: BodySize::Length(body_size),
293 consumed: false,
294 }
295 }
296}
297
298pub struct ListenerAnswers {
300 pub answer_301: Template,
302 pub answer_400: Template,
304 pub answer_401: Template,
306 pub answer_404: Template,
308 pub answer_408: Template,
310 pub answer_413: Template,
312 pub answer_502: Template,
314 pub answer_503: Template,
316 pub answer_504: Template,
318 pub answer_507: Template,
320}
321
322#[allow(non_snake_case)]
324pub struct ClusterAnswers {
325 pub answer_503: Template,
327}
328
329pub struct HttpAnswers {
330 pub listener_answers: ListenerAnswers, pub cluster_custom_answers: HashMap<ClusterId, ClusterAnswers>,
332}
333
334fn default_301() -> String {
348 String::from(
349 "\
350HTTP/1.1 301 Moved Permanently\r
351Location: %REDIRECT_LOCATION\r
352Connection: close\r
353Content-Length: 0\r
354Sozu-Id: %REQUEST_ID\r
355\r\n",
356 )
357}
358
359fn default_400() -> String {
360 String::from(
361 "\
362HTTP/1.1 400 Bad Request\r
363Cache-Control: no-cache\r
364Connection: close\r
365%Content-Length: %CONTENT_LENGTH\r
366Sozu-Id: %REQUEST_ID\r
367\r
368<html><head><meta charset='utf-8'><head><body>
369<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
370<h1>400 Bad Request</h1>
371<pre>
372{
373 \"status_code\": 400,
374 \"route\": \"%ROUTE\",
375 \"request_id\": \"%REQUEST_ID\",
376 \"parsing_phase\": \"%PHASE\",
377 \"successfully_parsed\": %SUCCESSFULLY_PARSED,
378 \"partially_parsed\": %PARTIALLY_PARSED,
379 \"invalid\": %INVALID
380}
381</pre>
382<p>Request could not be parsed. %MESSAGE</p>
383<div id=details hidden=true>
384<p>While parsing %PHASE, this was reported as invalid:</p>
385<pre>
386<span id=p1 style='background:rgba(0,255,0,0.2)'></span><span id=p2 style='background:rgba(255,255,0,0.2)'></span><span id=p3 style='background:rgba(255,0,0,0.2)'></span>
387</pre>
388</div>
389<script>
390function display(id, chunks) {
391 let [start, end] = chunks.split(' … ');
392 let dec = new TextDecoder('utf8');
393 let decode = chunk => dec.decode(new Uint8Array(chunk.split(' ').filter(e => e).map(e => parseInt(e, 16))));
394 document.getElementById(id).innerText = JSON.stringify(end ? `${decode(start)} […] ${decode(end)}` : `${decode(start)}`).slice(1,-1);
395}
396let p1 = %SUCCESSFULLY_PARSED;
397let p2 = %PARTIALLY_PARSED;
398let p3 = %INVALID;
399if (p1 !== null) {
400 document.getElementById('details').hidden=false;
401 display('p1', p1);display('p2', p2);display('p3', p3);
402}
403</script>
404<footer>This is an automatic answer by Sōzu.</footer></body></html>",
405 )
406}
407
408fn default_401() -> String {
409 String::from(
410 "\
411HTTP/1.1 401 Unauthorized\r
412Cache-Control: no-cache\r
413Connection: close\r
414Sozu-Id: %REQUEST_ID\r
415\r
416<html><head><meta charset='utf-8'><head><body>
417<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
418<h1>401 Unauthorized</h1>
419<pre>
420{
421 \"status_code\": 401,
422 \"route\": \"%ROUTE\",
423 \"request_id\": \"%REQUEST_ID\"
424}
425</pre>
426<footer>This is an automatic answer by Sōzu.</footer></body></html>",
427 )
428}
429
430fn default_404() -> String {
431 String::from(
432 "\
433HTTP/1.1 404 Not Found\r
434Cache-Control: no-cache\r
435Connection: close\r
436Sozu-Id: %REQUEST_ID\r
437\r
438<html><head><meta charset='utf-8'><head><body>
439<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
440<h1>404 Not Found</h1>
441<pre>
442{
443 \"status_code\": 404,
444 \"route\": \"%ROUTE\",
445 \"request_id\": \"%REQUEST_ID\"
446}
447</pre>
448<footer>This is an automatic answer by Sōzu.</footer></body></html>",
449 )
450}
451
452fn default_408() -> String {
453 String::from(
454 "\
455HTTP/1.1 408 Request Timeout\r
456Cache-Control: no-cache\r
457Connection: close\r
458Sozu-Id: %REQUEST_ID\r
459\r
460<html><head><meta charset='utf-8'><head><body>
461<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
462<h1>408 Request Timeout</h1>
463<pre>
464{
465 \"status_code\": 408,
466 \"route\": \"%ROUTE\",
467 \"request_id\": \"%REQUEST_ID\"
468}
469</pre>
470<p>Request timed out after %DURATION.</p>
471<footer>This is an automatic answer by Sōzu.</footer></body></html>",
472 )
473}
474
475fn default_413() -> String {
476 String::from(
477 "\
478HTTP/1.1 413 Payload Too Large\r
479Cache-Control: no-cache\r
480Connection: close\r
481%Content-Length: %CONTENT_LENGTH\r
482Sozu-Id: %REQUEST_ID\r
483\r
484<html><head><meta charset='utf-8'><head><body>
485<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
486<h1>413 Payload Too Large</h1>
487<pre>
488{
489 \"status_code\": 413,
490 \"route\": \"%ROUTE\",
491 \"request_id\": \"%REQUEST_ID\"
492}
493</pre>
494<p>Request needed more than %CAPACITY bytes to fit. Parser stopped at phase: %PHASE. %MESSAGE</p>
495<footer>This is an automatic answer by Sōzu.</footer></body></html>",
496 )
497}
498
499fn default_502() -> String {
500 String::from(
501 "\
502HTTP/1.1 502 Bad Gateway\r
503Cache-Control: no-cache\r
504Connection: close\r
505%Content-Length: %CONTENT_LENGTH\r
506Sozu-Id: %REQUEST_ID\r
507\r
508<html><head><meta charset='utf-8'><head><body>
509<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
510<h1>502 Bad Gateway</h1>
511<pre>
512{
513 \"status_code\": 502,
514 \"route\": \"%ROUTE\",
515 \"request_id\": \"%REQUEST_ID\",
516 \"cluster_id\": \"%CLUSTER_ID\",
517 \"backend_id\": \"%BACKEND_ID\",
518 \"parsing_phase\": \"%PHASE\",
519 \"successfully_parsed\": \"%SUCCESSFULLY_PARSED\",
520 \"partially_parsed\": \"%PARTIALLY_PARSED\",
521 \"invalid\": \"%INVALID\"
522}
523</pre>
524<p>Response could not be parsed. %MESSAGE</p>
525<div id=details hidden=true>
526<p>While parsing %PHASE, this was reported as invalid:</p>
527<pre>
528<span id=p1 style='background:rgba(0,255,0,0.2)'></span><span id=p2 style='background:rgba(255,255,0,0.2)'></span><span id=p3 style='background:rgba(255,0,0,0.2)'></span>
529</pre>
530</div>
531<script>
532function display(id, chunks) {
533 let [start, end] = chunks.split(' … ');
534 let dec = new TextDecoder('utf8');
535 let decode = chunk => dec.decode(new Uint8Array(chunk.split(' ').filter(e => e).map(e => parseInt(e, 16))));
536 document.getElementById(id).innerText = JSON.stringify(end ? `${decode(start)} […] ${decode(end)}` : `${decode(start)}`).slice(1,-1);
537}
538let p1 = %SUCCESSFULLY_PARSED;
539let p2 = %PARTIALLY_PARSED;
540let p3 = %INVALID;
541if (p1 !== null) {
542 document.getElementById('details').hidden=false;
543 display('p1', p1);display('p2', p2);display('p3', p3);
544}
545</script>
546<footer>This is an automatic answer by Sōzu.</footer></body></html>",
547 )
548}
549
550fn default_503() -> String {
551 String::from(
552 "\
553HTTP/1.1 503 Service Unavailable\r
554Cache-Control: no-cache\r
555Connection: close\r
556%Content-Length: %CONTENT_LENGTH\r
557Sozu-Id: %REQUEST_ID\r
558\r
559<html><head><meta charset='utf-8'><head><body>
560<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
561<h1>503 Service Unavailable</h1>
562<pre>
563{
564 \"status_code\": 503,
565 \"route\": \"%ROUTE\",
566 \"request_id\": \"%REQUEST_ID\",
567 \"cluster_id\": \"%CLUSTER_ID\",
568 \"backend_id\": \"%BACKEND_ID\"
569}
570</pre>
571<p>%MESSAGE</p>
572<footer>This is an automatic answer by Sōzu.</footer></body></html>",
573 )
574}
575
576fn default_504() -> String {
577 String::from(
578 "\
579HTTP/1.1 504 Gateway Timeout\r
580Cache-Control: no-cache\r
581Connection: close\r
582Sozu-Id: %REQUEST_ID\r
583\r
584<html><head><meta charset='utf-8'><head><body>
585<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
586<h1>504 Gateway Timeout</h1>
587<pre>
588{
589 \"status_code\": 504,
590 \"route\": \"%ROUTE\",
591 \"request_id\": \"%REQUEST_ID\",
592 \"cluster_id\": \"%CLUSTER_ID\",
593 \"backend_id\": \"%BACKEND_ID\"
594}
595</pre>
596<p>Response timed out after %DURATION.</p>
597<footer>This is an automatic answer by Sōzu.</footer></body></html>",
598 )
599}
600
601fn default_507() -> String {
602 String::from(
603 "\
604HTTP/1.1 507 Insufficient Storage\r
605Cache-Control: no-cache\r
606Connection: close\r
607%Content-Length: %CONTENT_LENGTH\r
608Sozu-Id: %REQUEST_ID\r
609\r
610<html><head><meta charset='utf-8'><head><body>
611<style>pre{background:#EEE;padding:10px;border:1px solid #AAA;border-radius: 5px;}</style>
612<h1>507 Insufficient Storage</h1>
613<pre>
614{
615 \"status_code\": 507,
616 \"route\": \"%ROUTE\",
617 \"request_id\": \"%REQUEST_ID\",
618 \"cluster_id\": \"%CLUSTER_ID\",
619 \"backend_id\": \"%BACKEND_ID\"
620}
621</pre>
622<p>Response needed more than %CAPACITY bytes to fit. Parser stopped at phase: %PHASE. %MESSAGE/p>
623<footer>This is an automatic answer by Sōzu.</footer></body></html>",
624 )
625}
626
627fn phase_to_vec(phase: ParsingPhaseMarker) -> Vec<u8> {
628 match phase {
629 ParsingPhaseMarker::StatusLine => "StatusLine",
630 ParsingPhaseMarker::Headers => "Headers",
631 ParsingPhaseMarker::Cookies => "Cookies",
632 ParsingPhaseMarker::Body => "Body",
633 ParsingPhaseMarker::Chunks => "Chunks",
634 ParsingPhaseMarker::Trailers => "Trailers",
635 ParsingPhaseMarker::Terminated => "Terminated",
636 ParsingPhaseMarker::Error => "Error",
637 }
638 .into()
639}
640
641impl HttpAnswers {
642 #[rustfmt::skip]
643 pub fn template(status: u16, answer: String) -> Result<Template, (u16, TemplateError)> {
644 let length = TemplateVariable {
645 name: "CONTENT_LENGTH",
646 valid_in_body: false,
647 valid_in_header: true,
648 typ: ReplacementType::ContentLength,
649 };
650
651 let route = TemplateVariable {
652 name: "ROUTE",
653 valid_in_body: true,
654 valid_in_header: true,
655 typ: ReplacementType::Variable(0),
656 };
657 let request_id = TemplateVariable {
658 name: "REQUEST_ID",
659 valid_in_body: true,
660 valid_in_header: true,
661 typ: ReplacementType::Variable(0),
662 };
663 let cluster_id = TemplateVariable {
664 name: "CLUSTER_ID",
665 valid_in_body: true,
666 valid_in_header: true,
667 typ: ReplacementType::Variable(0),
668 };
669 let backend_id = TemplateVariable {
670 name: "BACKEND_ID",
671 valid_in_body: true,
672 valid_in_header: true,
673 typ: ReplacementType::Variable(0),
674 };
675 let duration = TemplateVariable {
676 name: "DURATION",
677 valid_in_body: true,
678 valid_in_header: true,
679 typ: ReplacementType::Variable(0),
680 };
681 let capacity = TemplateVariable {
682 name: "CAPACITY",
683 valid_in_body: true,
684 valid_in_header: true,
685 typ: ReplacementType::Variable(0),
686 };
687 let phase = TemplateVariable {
688 name: "PHASE",
689 valid_in_body: true,
690 valid_in_header: true,
691 typ: ReplacementType::Variable(0),
692 };
693
694 let location = TemplateVariable {
695 name: "REDIRECT_LOCATION",
696 valid_in_body: false,
697 valid_in_header: true,
698 typ: ReplacementType::VariableOnce(0),
699 };
700 let message = TemplateVariable {
701 name: "MESSAGE",
702 valid_in_body: true,
703 valid_in_header: false,
704 typ: ReplacementType::VariableOnce(0),
705 };
706 let successfully_parsed = TemplateVariable {
707 name: "SUCCESSFULLY_PARSED",
708 valid_in_body: true,
709 valid_in_header: false,
710 typ: ReplacementType::Variable(0),
711 };
712 let partially_parsed = TemplateVariable {
713 name: "PARTIALLY_PARSED",
714 valid_in_body: true,
715 valid_in_header: false,
716 typ: ReplacementType::Variable(0),
717 };
718 let invalid = TemplateVariable {
719 name: "INVALID",
720 valid_in_body: true,
721 valid_in_header: false,
722 typ: ReplacementType::Variable(0),
723 };
724
725 match status {
726 301 => Template::new(
727 301,
728 answer,
729 &[length, route, request_id, location]
730 ),
731 400 => Template::new(
732 400,
733 answer,
734 &[length, route, request_id, message, phase, successfully_parsed, partially_parsed, invalid],
735 ),
736 401 => Template::new(
737 401,
738 answer,
739 &[length, route, request_id]
740 ),
741 404 => Template::new(
742 404,
743 answer,
744 &[length, route, request_id]
745 ),
746 408 => Template::new(
747 408,
748 answer,
749 &[length, route, request_id, duration]
750 ),
751 413 => Template::new(
752 413,
753 answer,
754 &[length, route, request_id, capacity, message, phase],
755 ),
756 502 => Template::new(
757 502,
758 answer,
759 &[length, route, request_id, cluster_id, backend_id, message, phase, successfully_parsed, partially_parsed, invalid],
760 ),
761 503 => Template::new(
762 503,
763 answer,
764 &[length, route, request_id, cluster_id, backend_id, message],
765 ),
766 504 => Template::new(
767 504,
768 answer,
769 &[length, route, request_id, cluster_id, backend_id, duration],
770 ),
771 507 => Template::new(
772 507,
773 answer,
774 &[length, route, request_id, cluster_id, backend_id, capacity, message, phase],
775 ),
776 _ => Err(TemplateError::InvalidStatusCode(status)),
777 }
778 .map_err(|e| (status, e))
779 }
780
781 pub fn new(conf: &Option<CustomHttpAnswers>) -> Result<Self, (u16, TemplateError)> {
782 Ok(HttpAnswers {
783 listener_answers: ListenerAnswers {
784 answer_301: Self::template(
785 301,
786 conf.as_ref()
787 .and_then(|c| c.answer_301.clone())
788 .unwrap_or(default_301()),
789 )?,
790 answer_400: Self::template(
791 400,
792 conf.as_ref()
793 .and_then(|c| c.answer_400.clone())
794 .unwrap_or(default_400()),
795 )?,
796 answer_401: Self::template(
797 401,
798 conf.as_ref()
799 .and_then(|c| c.answer_401.clone())
800 .unwrap_or(default_401()),
801 )?,
802 answer_404: Self::template(
803 404,
804 conf.as_ref()
805 .and_then(|c| c.answer_404.clone())
806 .unwrap_or(default_404()),
807 )?,
808 answer_408: Self::template(
809 408,
810 conf.as_ref()
811 .and_then(|c| c.answer_408.clone())
812 .unwrap_or(default_408()),
813 )?,
814 answer_413: Self::template(
815 413,
816 conf.as_ref()
817 .and_then(|c| c.answer_413.clone())
818 .unwrap_or(default_413()),
819 )?,
820 answer_502: Self::template(
821 502,
822 conf.as_ref()
823 .and_then(|c| c.answer_502.clone())
824 .unwrap_or(default_502()),
825 )?,
826 answer_503: Self::template(
827 503,
828 conf.as_ref()
829 .and_then(|c| c.answer_503.clone())
830 .unwrap_or(default_503()),
831 )?,
832 answer_504: Self::template(
833 504,
834 conf.as_ref()
835 .and_then(|c| c.answer_504.clone())
836 .unwrap_or(default_504()),
837 )?,
838 answer_507: Self::template(
839 507,
840 conf.as_ref()
841 .and_then(|c| c.answer_507.clone())
842 .unwrap_or(default_507()),
843 )?,
844 },
845 cluster_custom_answers: HashMap::new(),
846 })
847 }
848
849 pub fn add_custom_answer(
850 &mut self,
851 cluster_id: &str,
852 answer_503: String,
853 ) -> Result<(), (u16, TemplateError)> {
854 let answer_503 = Self::template(503, answer_503)?;
855 self.cluster_custom_answers
856 .insert(cluster_id.to_string(), ClusterAnswers { answer_503 });
857 Ok(())
858 }
859
860 pub fn remove_custom_answer(&mut self, cluster_id: &str) {
861 self.cluster_custom_answers.remove(cluster_id);
862 }
863
864 pub fn get(
865 &self,
866 answer: DefaultAnswer,
867 request_id: String,
868 cluster_id: Option<&str>,
869 backend_id: Option<&str>,
870 route: String,
871 ) -> DefaultAnswerStream {
872 let variables: Vec<Vec<u8>>;
873 let mut variables_once: Vec<Vec<u8>>;
874 let template = match answer {
875 DefaultAnswer::Answer301 { location } => {
876 variables = vec![route.into(), request_id.into()];
877 variables_once = vec![location.into()];
878 &self.listener_answers.answer_301
879 }
880 DefaultAnswer::Answer400 {
881 message,
882 phase,
883 successfully_parsed,
884 partially_parsed,
885 invalid,
886 } => {
887 variables = vec![
888 route.into(),
889 request_id.into(),
890 phase_to_vec(phase),
891 successfully_parsed.into(),
892 partially_parsed.into(),
893 invalid.into(),
894 ];
895 variables_once = vec![message.into()];
896 &self.listener_answers.answer_400
897 }
898 DefaultAnswer::Answer401 {} => {
899 variables = vec![route.into(), request_id.into()];
900 variables_once = vec![];
901 &self.listener_answers.answer_401
902 }
903 DefaultAnswer::Answer404 {} => {
904 variables = vec![route.into(), request_id.into()];
905 variables_once = vec![];
906 &self.listener_answers.answer_404
907 }
908 DefaultAnswer::Answer408 { duration } => {
909 variables = vec![route.into(), request_id.into(), duration.to_string().into()];
910 variables_once = vec![];
911 &self.listener_answers.answer_408
912 }
913 DefaultAnswer::Answer413 {
914 message,
915 phase,
916 capacity,
917 } => {
918 variables = vec![
919 route.into(),
920 request_id.into(),
921 capacity.to_string().into(),
922 phase_to_vec(phase),
923 ];
924 variables_once = vec![message.into()];
925 &self.listener_answers.answer_413
926 }
927 DefaultAnswer::Answer502 {
928 message,
929 phase,
930 successfully_parsed,
931 partially_parsed,
932 invalid,
933 } => {
934 variables = vec![
935 route.into(),
936 request_id.into(),
937 cluster_id.unwrap_or_default().into(),
938 backend_id.unwrap_or_default().into(),
939 phase_to_vec(phase),
940 successfully_parsed.into(),
941 partially_parsed.into(),
942 invalid.into(),
943 ];
944 variables_once = vec![message.into()];
945 &self.listener_answers.answer_502
946 }
947 DefaultAnswer::Answer503 { message } => {
948 variables = vec![
949 route.into(),
950 request_id.into(),
951 cluster_id.unwrap_or_default().into(),
952 backend_id.unwrap_or_default().into(),
953 ];
954 variables_once = vec![message.into()];
955 cluster_id
956 .and_then(|id: &str| self.cluster_custom_answers.get(id))
957 .map(|c| &c.answer_503)
958 .unwrap_or_else(|| &self.listener_answers.answer_503)
959 }
960 DefaultAnswer::Answer504 { duration } => {
961 variables = vec![
962 route.into(),
963 request_id.into(),
964 cluster_id.unwrap_or_default().into(),
965 backend_id.unwrap_or_default().into(),
966 duration.to_string().into(),
967 ];
968 variables_once = vec![];
969 &self.listener_answers.answer_504
970 }
971 DefaultAnswer::Answer507 {
972 phase,
973 message,
974 capacity,
975 } => {
976 variables = vec![
977 route.into(),
978 request_id.into(),
979 cluster_id.unwrap_or_default().into(),
980 backend_id.unwrap_or_default().into(),
981 capacity.to_string().into(),
982 phase_to_vec(phase),
983 ];
984 variables_once = vec![message.into()];
985 &self.listener_answers.answer_507
986 }
987 };
988 template.fill(&variables, &mut variables_once)
991 }
992}