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