xref: /cloud-hypervisor/api_client/src/lib.rs (revision cd0208fe0a92d0ee695f72c7e8c0010afb5cebc8)
1 // Copyright © 2020 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 
6 use std::fmt;
7 use std::io::{Read, Write};
8 use std::os::unix::io::RawFd;
9 use vmm_sys_util::sock_ctrl_msg::ScmSocket;
10 
11 #[derive(Debug)]
12 pub enum Error {
13     Socket(std::io::Error),
14     SocketSendFds(vmm_sys_util::errno::Error),
15     StatusCodeParsing(std::num::ParseIntError),
16     MissingProtocol,
17     ContentLengthParsing(std::num::ParseIntError),
18     ServerResponse(StatusCode, Option<String>),
19 }
20 
21 impl fmt::Display for Error {
22     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23         use Error::*;
24         match self {
25             Socket(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
26             SocketSendFds(e) => write!(f, "Error writing to or reading from HTTP socket: {}", e),
27             StatusCodeParsing(e) => write!(f, "Error parsing HTTP status code: {}", e),
28             MissingProtocol => write!(f, "HTTP output is missing protocol statement"),
29             ContentLengthParsing(e) => write!(f, "Error parsing HTTP Content-Length field: {}", e),
30             ServerResponse(s, o) => {
31                 if let Some(o) = o {
32                     write!(f, "Server responded with an error: {:?}: {}", s, o)
33                 } else {
34                     write!(f, "Server responded with an error: {:?}", s)
35                 }
36             }
37         }
38     }
39 }
40 
41 #[derive(Clone, Copy, Debug)]
42 pub enum StatusCode {
43     Continue,
44     Ok,
45     NoContent,
46     BadRequest,
47     NotFound,
48     InternalServerError,
49     NotImplemented,
50     Unknown,
51 }
52 
53 impl StatusCode {
54     fn from_raw(code: usize) -> StatusCode {
55         match code {
56             100 => StatusCode::Continue,
57             200 => StatusCode::Ok,
58             204 => StatusCode::NoContent,
59             400 => StatusCode::BadRequest,
60             404 => StatusCode::NotFound,
61             500 => StatusCode::InternalServerError,
62             501 => StatusCode::NotImplemented,
63             _ => StatusCode::Unknown,
64         }
65     }
66 
67     fn parse(code: &str) -> Result<StatusCode, Error> {
68         Ok(StatusCode::from_raw(
69             code.trim().parse().map_err(Error::StatusCodeParsing)?,
70         ))
71     }
72 
73     fn is_server_error(self) -> bool {
74         !matches!(
75             self,
76             StatusCode::Ok | StatusCode::Continue | StatusCode::NoContent
77         )
78     }
79 }
80 
81 fn get_header<'a>(res: &'a str, header: &'a str) -> Option<&'a str> {
82     let header_str = format!("{}: ", header);
83     res.find(&header_str)
84         .map(|o| &res[o + header_str.len()..o + res[o..].find('\r').unwrap()])
85 }
86 
87 fn get_status_code(res: &str) -> Result<StatusCode, Error> {
88     if let Some(o) = res.find("HTTP/1.1") {
89         Ok(StatusCode::parse(
90             &res[o + "HTTP/1.1 ".len()..res[o..].find('\r').unwrap()],
91         )?)
92     } else {
93         Err(Error::MissingProtocol)
94     }
95 }
96 
97 fn parse_http_response(socket: &mut dyn Read) -> Result<Option<String>, Error> {
98     let mut res = String::new();
99     let mut body_offset = None;
100     let mut content_length: Option<usize> = None;
101     loop {
102         let mut bytes = vec![0; 256];
103         let count = socket.read(&mut bytes).map_err(Error::Socket)?;
104         // If the return value is 0, the peer has performed an orderly shutdown.
105         if count == 0 {
106             break;
107         }
108         res.push_str(std::str::from_utf8(&bytes[0..count]).unwrap());
109 
110         // End of headers
111         if let Some(o) = res.find("\r\n\r\n") {
112             body_offset = Some(o + "\r\n\r\n".len());
113 
114             // With all headers available we can see if there is any body
115             content_length = if let Some(length) = get_header(&res, "Content-Length") {
116                 Some(length.trim().parse().map_err(Error::ContentLengthParsing)?)
117             } else {
118                 None
119             };
120 
121             if content_length.is_none() {
122                 break;
123             }
124         }
125 
126         if let Some(body_offset) = body_offset {
127             if let Some(content_length) = content_length {
128                 if res.len() >= content_length + body_offset {
129                     break;
130                 }
131             }
132         }
133     }
134     let body_string = content_length.and(body_offset.map(|o| String::from(&res[o..])));
135     let status_code = get_status_code(&res)?;
136 
137     if status_code.is_server_error() {
138         Err(Error::ServerResponse(status_code, body_string))
139     } else {
140         Ok(body_string)
141     }
142 }
143 
144 /// Make an API request using the fully qualified command name.
145 /// For example, full_command could be "vm.create" or "vmm.ping".
146 pub fn simple_api_full_command_with_fds_and_response<T: Read + Write + ScmSocket>(
147     socket: &mut T,
148     method: &str,
149     full_command: &str,
150     request_body: Option<&str>,
151     request_fds: Vec<RawFd>,
152 ) -> Result<Option<String>, Error> {
153     socket
154         .send_with_fds(
155             &[format!(
156                 "{} /api/v1/{} HTTP/1.1\r\nHost: localhost\r\nAccept: */*\r\n",
157                 method, full_command
158             )
159             .as_bytes()],
160             &request_fds,
161         )
162         .map_err(Error::SocketSendFds)?;
163 
164     if let Some(request_body) = request_body {
165         socket
166             .write_all(format!("Content-Length: {}\r\n", request_body.len()).as_bytes())
167             .map_err(Error::Socket)?;
168     }
169 
170     socket.write_all(b"\r\n").map_err(Error::Socket)?;
171 
172     if let Some(request_body) = request_body {
173         socket
174             .write_all(request_body.as_bytes())
175             .map_err(Error::Socket)?;
176     }
177 
178     socket.flush().map_err(Error::Socket)?;
179 
180     parse_http_response(socket)
181 }
182 
183 pub fn simple_api_full_command_with_fds<T: Read + Write + ScmSocket>(
184     socket: &mut T,
185     method: &str,
186     full_command: &str,
187     request_body: Option<&str>,
188     request_fds: Vec<RawFd>,
189 ) -> Result<(), Error> {
190     let response = simple_api_full_command_with_fds_and_response(
191         socket,
192         method,
193         full_command,
194         request_body,
195         request_fds,
196     )?;
197 
198     if response.is_some() {
199         println!("{}", response.unwrap());
200     }
201 
202     Ok(())
203 }
204 
205 pub fn simple_api_full_command<T: Read + Write + ScmSocket>(
206     socket: &mut T,
207     method: &str,
208     full_command: &str,
209     request_body: Option<&str>,
210 ) -> Result<(), Error> {
211     simple_api_full_command_with_fds(socket, method, full_command, request_body, Vec::new())
212 }
213 
214 pub fn simple_api_full_command_and_response<T: Read + Write + ScmSocket>(
215     socket: &mut T,
216     method: &str,
217     full_command: &str,
218     request_body: Option<&str>,
219 ) -> Result<Option<String>, Error> {
220     simple_api_full_command_with_fds_and_response(
221         socket,
222         method,
223         full_command,
224         request_body,
225         Vec::new(),
226     )
227 }
228 
229 pub fn simple_api_command_with_fds<T: Read + Write + ScmSocket>(
230     socket: &mut T,
231     method: &str,
232     c: &str,
233     request_body: Option<&str>,
234     request_fds: Vec<RawFd>,
235 ) -> Result<(), Error> {
236     // Create the full VM command. For VMM commands, use
237     // simple_api_full_command().
238     let full_command = format!("vm.{}", c);
239 
240     simple_api_full_command_with_fds(socket, method, &full_command, request_body, request_fds)
241 }
242 
243 pub fn simple_api_command<T: Read + Write + ScmSocket>(
244     socket: &mut T,
245     method: &str,
246     c: &str,
247     request_body: Option<&str>,
248 ) -> Result<(), Error> {
249     simple_api_command_with_fds(socket, method, c, request_body, Vec::new())
250 }
251