Add support for intercepting HTTP responses

This commit is contained in:
David Stotijn
2022-03-18 16:40:05 +01:00
parent f4074a8060
commit cf55456c42
16 changed files with 1627 additions and 167 deletions

View File

@ -1,4 +1,5 @@
import CancelIcon from "@mui/icons-material/Cancel"; import CancelIcon from "@mui/icons-material/Cancel";
import DownloadIcon from "@mui/icons-material/Download";
import SendIcon from "@mui/icons-material/Send"; import SendIcon from "@mui/icons-material/Send";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
import { Alert, Box, Button, CircularProgress, IconButton, Tooltip, Typography } from "@mui/material"; import { Alert, Box, Button, CircularProgress, IconButton, Tooltip, Typography } from "@mui/material";
@ -9,15 +10,17 @@ import { useInterceptedRequests } from "lib/InterceptedRequestsContext";
import { KeyValuePair, sortKeyValuePairs } from "lib/components/KeyValuePair"; import { KeyValuePair, sortKeyValuePairs } from "lib/components/KeyValuePair";
import Link from "lib/components/Link"; import Link from "lib/components/Link";
import RequestTabs from "lib/components/RequestTabs"; import RequestTabs from "lib/components/RequestTabs";
import Response from "lib/components/Response"; import ResponseStatus from "lib/components/ResponseStatus";
import SplitPane from "lib/components/SplitPane"; import ResponseTabs from "lib/components/ResponseTabs";
import UrlBar, { HttpMethod, HttpProto, httpProtoMap } from "lib/components/UrlBar"; import UrlBar, { HttpMethod, HttpProto, httpProtoMap } from "lib/components/UrlBar";
import { import {
HttpProtocol, HttpProtocol,
HttpRequest, HttpRequest,
useCancelRequestMutation, useCancelRequestMutation,
useCancelResponseMutation,
useGetInterceptedRequestQuery, useGetInterceptedRequestQuery,
useModifyRequestMutation, useModifyRequestMutation,
useModifyResponseMutation,
} from "lib/graphql/generated"; } from "lib/graphql/generated";
import { queryParamsFromURL } from "lib/queryParamsFromURL"; import { queryParamsFromURL } from "lib/queryParamsFromURL";
import updateKeyPairItem from "lib/updateKeyPairItem"; import updateKeyPairItem from "lib/updateKeyPairItem";
@ -43,8 +46,10 @@ function EditRequest(): JSX.Element {
const [url, setURL] = useState(""); const [url, setURL] = useState("");
const [proto, setProto] = useState(HttpProto.Http20); const [proto, setProto] = useState(HttpProto.Http20);
const [queryParams, setQueryParams] = useState<KeyValuePair[]>([{ key: "", value: "" }]); const [queryParams, setQueryParams] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
const [headers, setHeaders] = useState<KeyValuePair[]>([{ key: "", value: "" }]); const [reqHeaders, setReqHeaders] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
const [body, setBody] = useState(""); const [resHeaders, setResHeaders] = useState<KeyValuePair[]>([{ key: "", value: "" }]);
const [reqBody, setReqBody] = useState("");
const [resBody, setResBody] = useState("");
const handleQueryParamChange = (key: string, value: string, idx: number) => { const handleQueryParamChange = (key: string, value: string, idx: number) => {
setQueryParams((prev) => { setQueryParams((prev) => {
@ -61,11 +66,18 @@ function EditRequest(): JSX.Element {
}); });
}; };
const handleHeaderChange = (key: string, value: string, idx: number) => { const handleReqHeaderChange = (key: string, value: string, idx: number) => {
setHeaders((prev) => updateKeyPairItem(key, value, idx, prev)); setReqHeaders((prev) => updateKeyPairItem(key, value, idx, prev));
}; };
const handleHeaderDelete = (idx: number) => { const handleReqHeaderDelete = (idx: number) => {
setHeaders((prev) => prev.slice(0, idx).concat(prev.slice(idx + 1, prev.length))); setReqHeaders((prev) => prev.slice(0, idx).concat(prev.slice(idx + 1, prev.length)));
};
const handleResHeaderChange = (key: string, value: string, idx: number) => {
setResHeaders((prev) => updateKeyPairItem(key, value, idx, prev));
};
const handleResHeaderDelete = (idx: number) => {
setResHeaders((prev) => prev.slice(0, idx).concat(prev.slice(idx + 1, prev.length)));
}; };
const handleURLChange = (url: string) => { const handleURLChange = (url: string) => {
@ -93,38 +105,44 @@ function EditRequest(): JSX.Element {
setURL(interceptedRequest.url); setURL(interceptedRequest.url);
setMethod(interceptedRequest.method); setMethod(interceptedRequest.method);
setBody(interceptedRequest.body || ""); setReqBody(interceptedRequest.body || "");
const newQueryParams = queryParamsFromURL(interceptedRequest.url); const newQueryParams = queryParamsFromURL(interceptedRequest.url);
// Push empty row. // Push empty row.
newQueryParams.push({ key: "", value: "" }); newQueryParams.push({ key: "", value: "" });
setQueryParams(newQueryParams); setQueryParams(newQueryParams);
const newHeaders = sortKeyValuePairs(interceptedRequest.headers || []); const newReqHeaders = sortKeyValuePairs(interceptedRequest.headers || []);
setHeaders([...newHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]); setReqHeaders([...newReqHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
setResBody(interceptedRequest.response?.body || "");
const newResHeaders = sortKeyValuePairs(interceptedRequest.response?.headers || []);
setResHeaders([...newResHeaders.map(({ key, value }) => ({ key, value })), { key: "", value: "" }]);
}, },
}); });
const interceptedReq = reqId ? getReqResult?.data?.interceptedRequest : undefined; const interceptedReq =
reqId && !getReqResult?.data?.interceptedRequest?.response ? getReqResult?.data?.interceptedRequest : undefined;
const interceptedRes = reqId ? getReqResult?.data?.interceptedRequest?.response : undefined;
const [modifyRequest, modifyResult] = useModifyRequestMutation(); const [modifyRequest, modifyReqResult] = useModifyRequestMutation();
const [cancelRequest, cancelResult] = useCancelRequestMutation(); const [cancelRequest, cancelReqResult] = useCancelRequestMutation();
const [modifyResponse, modifyResResult] = useModifyResponseMutation();
const [cancelResponse, cancelResResult] = useCancelResponseMutation();
const onActionCompleted = () => { const onActionCompleted = () => {
setURL(""); setURL("");
setMethod(HttpMethod.Get); setMethod(HttpMethod.Get);
setBody(""); setReqBody("");
setQueryParams([]); setQueryParams([]);
setHeaders([]); setReqHeaders([]);
router.replace(`/proxy/intercept`); router.replace(`/proxy/intercept`);
}; };
const handleFormSubmit: React.FormEventHandler = (e) => { const handleFormSubmit: React.FormEventHandler = (e) => {
e.preventDefault(); e.preventDefault();
if (!interceptedReq) { if (interceptedReq) {
return;
}
modifyRequest({ modifyRequest({
variables: { variables: {
request: { request: {
@ -132,8 +150,8 @@ function EditRequest(): JSX.Element {
url, url,
method, method,
proto: httpProtoMap.get(proto) || HttpProtocol.Http20, proto: httpProtoMap.get(proto) || HttpProtocol.Http20,
headers: headers.filter((kv) => kv.key !== ""), headers: reqHeaders.filter((kv) => kv.key !== ""),
body: body || undefined, body: reqBody || undefined,
}, },
}, },
update(cache) { update(cache) {
@ -147,9 +165,35 @@ function EditRequest(): JSX.Element {
}, },
onCompleted: onActionCompleted, onCompleted: onActionCompleted,
}); });
}
if (interceptedRes) {
modifyResponse({
variables: {
response: {
requestID: interceptedRes.id,
proto: interceptedRes.proto, // TODO: Allow modifying
statusCode: interceptedRes.statusCode, // TODO: Allow modifying
statusReason: interceptedRes.statusReason, // TODO: Allow modifying
headers: resHeaders.filter((kv) => kv.key !== ""),
body: resBody || undefined,
},
},
update(cache) {
cache.modify({
fields: {
interceptedRequests(existing: HttpRequest[], { readField }) {
return existing.filter((ref) => interceptedRes.id !== readField("id", ref));
},
},
});
},
onCompleted: onActionCompleted,
});
}
}; };
const handleCancelClick = () => { const handleReqCancelClick = () => {
if (!interceptedReq) { if (!interceptedReq) {
return; return;
} }
@ -171,6 +215,28 @@ function EditRequest(): JSX.Element {
}); });
}; };
const handleResCancelClick = () => {
if (!interceptedRes) {
return;
}
cancelResponse({
variables: {
requestID: interceptedRes.id,
},
update(cache) {
cache.modify({
fields: {
interceptedRequests(existing: HttpRequest[], { readField }) {
return existing.filter((ref) => interceptedRes.id !== readField("id", ref));
},
},
});
},
onCompleted: onActionCompleted,
});
};
return ( return (
<Box display="flex" flexDirection="column" height="100%" gap={2}> <Box display="flex" flexDirection="column" height="100%" gap={2}>
<Box component="form" autoComplete="off" onSubmit={handleFormSubmit}> <Box component="form" autoComplete="off" onSubmit={handleFormSubmit}>
@ -184,12 +250,14 @@ function EditRequest(): JSX.Element {
onProtoChange={interceptedReq ? setProto : undefined} onProtoChange={interceptedReq ? setProto : undefined}
sx={{ flex: "1 auto" }} sx={{ flex: "1 auto" }}
/> />
{!interceptedRes && (
<>
<Button <Button
variant="contained" variant="contained"
disableElevation disableElevation
type="submit" type="submit"
disabled={!interceptedReq || modifyResult.loading || cancelResult.loading} disabled={!interceptedReq || modifyReqResult.loading || cancelReqResult.loading}
startIcon={modifyResult.loading ? <CircularProgress size={22} /> : <SendIcon />} startIcon={modifyReqResult.loading ? <CircularProgress size={22} /> : <SendIcon />}
> >
Send Send
</Button> </Button>
@ -197,51 +265,99 @@ function EditRequest(): JSX.Element {
variant="contained" variant="contained"
color="error" color="error"
disableElevation disableElevation
onClick={handleCancelClick} onClick={handleReqCancelClick}
disabled={!interceptedReq || modifyResult.loading || cancelResult.loading} disabled={!interceptedReq || modifyReqResult.loading || cancelReqResult.loading}
startIcon={cancelResult.loading ? <CircularProgress size={22} /> : <CancelIcon />} startIcon={cancelReqResult.loading ? <CircularProgress size={22} /> : <CancelIcon />}
> >
Cancel Cancel
</Button> </Button>
</>
)}
{interceptedRes && (
<>
<Button
variant="contained"
disableElevation
type="submit"
disabled={modifyResResult.loading || cancelResResult.loading}
endIcon={modifyResResult.loading ? <CircularProgress size={22} /> : <DownloadIcon />}
>
Receive
</Button>
<Button
variant="contained"
color="error"
disableElevation
onClick={handleResCancelClick}
disabled={modifyResResult.loading || cancelResResult.loading}
endIcon={cancelResResult.loading ? <CircularProgress size={22} /> : <CancelIcon />}
>
Cancel
</Button>
</>
)}
<Tooltip title="Intercept settings"> <Tooltip title="Intercept settings">
<IconButton LinkComponent={Link} href="/settings#intercept"> <IconButton LinkComponent={Link} href="/settings#intercept">
<SettingsIcon /> <SettingsIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</Box> </Box>
{modifyResult.error && ( {modifyReqResult.error && (
<Alert severity="error" sx={{ mt: 1 }}> <Alert severity="error" sx={{ mt: 1 }}>
{modifyResult.error.message} {modifyReqResult.error.message}
</Alert> </Alert>
)} )}
{cancelResult.error && ( {cancelReqResult.error && (
<Alert severity="error" sx={{ mt: 1 }}> <Alert severity="error" sx={{ mt: 1 }}>
{cancelResult.error.message} {cancelReqResult.error.message}
</Alert> </Alert>
)} )}
</Box> </Box>
<Box flex="1 auto" position="relative"> <Box flex="1 auto" overflow="scroll">
<SplitPane split="vertical" size={"50%"}> {interceptedReq && (
<Box sx={{ height: "100%", mr: 2, pb: 2, position: "relative" }}> <Box sx={{ height: "100%", pb: 2 }}>
<Typography variant="overline" color="textSecondary" sx={{ position: "absolute", right: 0, mt: 1.2 }}> <Typography variant="overline" color="textSecondary" sx={{ position: "absolute", right: 0, mt: 1.2 }}>
Request Request
</Typography> </Typography>
<RequestTabs <RequestTabs
queryParams={interceptedReq ? queryParams : []} queryParams={interceptedReq ? queryParams : []}
headers={interceptedReq ? headers : []} headers={interceptedReq ? reqHeaders : []}
body={body} body={reqBody}
onQueryParamChange={interceptedReq ? handleQueryParamChange : undefined} onQueryParamChange={interceptedReq ? handleQueryParamChange : undefined}
onQueryParamDelete={interceptedReq ? handleQueryParamDelete : undefined} onQueryParamDelete={interceptedReq ? handleQueryParamDelete : undefined}
onHeaderChange={interceptedReq ? handleHeaderChange : undefined} onHeaderChange={interceptedReq ? handleReqHeaderChange : undefined}
onHeaderDelete={interceptedReq ? handleHeaderDelete : undefined} onHeaderDelete={interceptedReq ? handleReqHeaderDelete : undefined}
onBodyChange={interceptedReq ? setBody : undefined} onBodyChange={interceptedReq ? setReqBody : undefined}
/> />
</Box> </Box>
<Box sx={{ height: "100%", position: "relative", ml: 2, pb: 2 }}> )}
<Response response={null} /> {interceptedRes && (
<Box sx={{ height: "100%", pb: 2 }}>
<Box sx={{ position: "absolute", right: 0, mt: 1.4 }}>
<Typography variant="overline" color="textSecondary" sx={{ float: "right", ml: 3 }}>
Response
</Typography>
{interceptedRes && (
<Box sx={{ float: "right", mt: 0.2 }}>
<ResponseStatus
proto={interceptedRes.proto}
statusCode={interceptedRes.statusCode}
statusReason={interceptedRes.statusReason}
/>
</Box> </Box>
</SplitPane> )}
</Box>
<ResponseTabs
headers={interceptedRes ? resHeaders : []}
body={resBody}
onHeaderChange={interceptedRes ? handleResHeaderChange : undefined}
onHeaderDelete={interceptedRes ? handleResHeaderDelete : undefined}
onBodyChange={interceptedRes ? setResBody : undefined}
hasResponse={interceptedRes !== undefined && interceptedRes !== null}
/>
</Box>
)}
</Box> </Box>
</Box> </Box>
); );

View File

@ -0,0 +1,5 @@
mutation CancelResponse($requestID: ID!) {
cancelResponse(requestID: $requestID) {
success
}
}

View File

@ -9,5 +9,16 @@ query GetInterceptedRequest($id: ID!) {
value value
} }
body body
response {
id
proto
statusCode
statusReason
headers {
key
value
}
body
}
} }
} }

View File

@ -0,0 +1,5 @@
mutation ModifyResponse($response: ModifyResponseInput!) {
modifyResponse(response: $response) {
success
}
}

View File

@ -2,13 +2,16 @@ import { TabContext, TabList, TabPanel } from "@mui/lab";
import { Box, Paper, Tab, Typography } from "@mui/material"; import { Box, Paper, Tab, Typography } from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { KeyValuePairTable, KeyValuePair, KeyValuePairTableProps } from "./KeyValuePair";
import Editor from "lib/components/Editor"; import Editor from "lib/components/Editor";
import { KeyValuePairTable } from "lib/components/KeyValuePair";
import { HttpResponseLog } from "lib/graphql/generated";
interface ResponseTabsProps { interface ResponseTabsProps {
headers: HttpResponseLog["headers"]; headers: KeyValuePair[];
body: HttpResponseLog["body"]; onHeaderChange?: KeyValuePairTableProps["onChange"];
onHeaderDelete?: KeyValuePairTableProps["onDelete"];
body?: string | null;
onBodyChange?: (value: string) => void;
hasResponse: boolean; hasResponse: boolean;
} }
@ -24,7 +27,7 @@ const reqNotSent = (
); );
function ResponseTabs(props: ResponseTabsProps): JSX.Element { function ResponseTabs(props: ResponseTabsProps): JSX.Element {
const { headers, body, hasResponse } = props; const { headers, onHeaderChange, onHeaderDelete, body, onBodyChange, hasResponse } = props;
const [tabValue, setTabValue] = useState(TabValue.Body); const [tabValue, setTabValue] = useState(TabValue.Body);
const contentType = headers.find((header) => header.key.toLowerCase() === "content-type")?.value; const contentType = headers.find((header) => header.key.toLowerCase() === "content-type")?.value;
@ -33,6 +36,8 @@ function ResponseTabs(props: ResponseTabsProps): JSX.Element {
textTransform: "none", textTransform: "none",
}; };
const headersLength = onHeaderChange ? headers.length - 1 : headers.length;
return ( return (
<Box height="100%" sx={{ display: "flex", flexDirection: "column" }}> <Box height="100%" sx={{ display: "flex", flexDirection: "column" }}>
<TabContext value={tabValue}> <TabContext value={tabValue}>
@ -43,20 +48,25 @@ function ResponseTabs(props: ResponseTabsProps): JSX.Element {
label={"Body" + (body?.length ? ` (${body.length} byte` + (body.length > 1 ? "s" : "") + ")" : "")} label={"Body" + (body?.length ? ` (${body.length} byte` + (body.length > 1 ? "s" : "") + ")" : "")}
sx={tabSx} sx={tabSx}
/> />
<Tab <Tab value={TabValue.Headers} label={"Headers" + (headersLength ? ` (${headersLength})` : "")} sx={tabSx} />
value={TabValue.Headers}
label={"Headers" + (headers.length ? ` (${headers.length})` : "")}
sx={tabSx}
/>
</TabList> </TabList>
</Box> </Box>
<Box flex="1 auto" overflow="hidden"> <Box flex="1 auto" overflow="hidden">
<TabPanel value={TabValue.Body} sx={{ p: 0, height: "100%" }}> <TabPanel value={TabValue.Body} sx={{ p: 0, height: "100%" }}>
{body && <Editor content={body} contentType={contentType} />} {hasResponse && (
<Editor
content={body || ""}
onChange={(value) => {
onBodyChange && onBodyChange(value || "");
}}
monacoOptions={{ readOnly: onBodyChange === undefined }}
contentType={contentType}
/>
)}
{!hasResponse && reqNotSent} {!hasResponse && reqNotSent}
</TabPanel> </TabPanel>
<TabPanel value={TabValue.Headers} sx={{ p: 0, height: "100%", overflow: "scroll" }}> <TabPanel value={TabValue.Headers} sx={{ p: 0, height: "100%", overflow: "scroll" }}>
{headers.length > 0 && <KeyValuePairTable items={headers} />} {hasResponse && <KeyValuePairTable items={headers} onChange={onHeaderChange} onDelete={onHeaderDelete} />}
{!hasResponse && reqNotSent} {!hasResponse && reqNotSent}
</TabPanel> </TabPanel>
</Box> </Box>

View File

@ -23,6 +23,11 @@ export type CancelRequestResult = {
success: Scalars['Boolean']; success: Scalars['Boolean'];
}; };
export type CancelResponseResult = {
__typename?: 'CancelResponseResult';
success: Scalars['Boolean'];
};
export type ClearHttpRequestLogResult = { export type ClearHttpRequestLogResult = {
__typename?: 'ClearHTTPRequestLogResult'; __typename?: 'ClearHTTPRequestLogResult';
success: Scalars['Boolean']; success: Scalars['Boolean'];
@ -79,6 +84,7 @@ export type HttpRequest = {
id: Scalars['ID']; id: Scalars['ID'];
method: HttpMethod; method: HttpMethod;
proto: HttpProtocol; proto: HttpProtocol;
response?: Maybe<HttpResponse>;
url: Scalars['URL']; url: Scalars['URL'];
}; };
@ -105,6 +111,17 @@ export type HttpRequestLogFilterInput = {
searchExpression?: InputMaybe<Scalars['String']>; searchExpression?: InputMaybe<Scalars['String']>;
}; };
export type HttpResponse = {
__typename?: 'HttpResponse';
body?: Maybe<Scalars['String']>;
headers: Array<HttpHeader>;
/** Will be the same ID as its related request ID. */
id: Scalars['ID'];
proto: HttpProtocol;
statusCode: Scalars['Int'];
statusReason: Scalars['String'];
};
export type HttpResponseLog = { export type HttpResponseLog = {
__typename?: 'HttpResponseLog'; __typename?: 'HttpResponseLog';
body?: Maybe<Scalars['String']>; body?: Maybe<Scalars['String']>;
@ -127,6 +144,7 @@ export type ModifyRequestInput = {
headers?: InputMaybe<Array<HttpHeaderInput>>; headers?: InputMaybe<Array<HttpHeaderInput>>;
id: Scalars['ID']; id: Scalars['ID'];
method: HttpMethod; method: HttpMethod;
modifyResponse?: InputMaybe<Scalars['Boolean']>;
proto: HttpProtocol; proto: HttpProtocol;
url: Scalars['URL']; url: Scalars['URL'];
}; };
@ -136,9 +154,24 @@ export type ModifyRequestResult = {
success: Scalars['Boolean']; success: Scalars['Boolean'];
}; };
export type ModifyResponseInput = {
body?: InputMaybe<Scalars['String']>;
headers?: InputMaybe<Array<HttpHeaderInput>>;
proto: HttpProtocol;
requestID: Scalars['ID'];
statusCode: Scalars['Int'];
statusReason: Scalars['String'];
};
export type ModifyResponseResult = {
__typename?: 'ModifyResponseResult';
success: Scalars['Boolean'];
};
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
cancelRequest: CancelRequestResult; cancelRequest: CancelRequestResult;
cancelResponse: CancelResponseResult;
clearHTTPRequestLog: ClearHttpRequestLogResult; clearHTTPRequestLog: ClearHttpRequestLogResult;
closeProject: CloseProjectResult; closeProject: CloseProjectResult;
createOrUpdateSenderRequest: SenderRequest; createOrUpdateSenderRequest: SenderRequest;
@ -147,6 +180,7 @@ export type Mutation = {
deleteProject: DeleteProjectResult; deleteProject: DeleteProjectResult;
deleteSenderRequests: DeleteSenderRequestsResult; deleteSenderRequests: DeleteSenderRequestsResult;
modifyRequest: ModifyRequestResult; modifyRequest: ModifyRequestResult;
modifyResponse: ModifyResponseResult;
openProject?: Maybe<Project>; openProject?: Maybe<Project>;
sendRequest: SenderRequest; sendRequest: SenderRequest;
setHttpRequestLogFilter?: Maybe<HttpRequestLogFilter>; setHttpRequestLogFilter?: Maybe<HttpRequestLogFilter>;
@ -161,6 +195,11 @@ export type MutationCancelRequestArgs = {
}; };
export type MutationCancelResponseArgs = {
requestID: Scalars['ID'];
};
export type MutationCreateOrUpdateSenderRequestArgs = { export type MutationCreateOrUpdateSenderRequestArgs = {
request: SenderRequestInput; request: SenderRequestInput;
}; };
@ -186,6 +225,11 @@ export type MutationModifyRequestArgs = {
}; };
export type MutationModifyResponseArgs = {
response: ModifyResponseInput;
};
export type MutationOpenProjectArgs = { export type MutationOpenProjectArgs = {
id: Scalars['ID']; id: Scalars['ID'];
}; };
@ -326,12 +370,19 @@ export type CancelRequestMutationVariables = Exact<{
export type CancelRequestMutation = { __typename?: 'Mutation', cancelRequest: { __typename?: 'CancelRequestResult', success: boolean } }; export type CancelRequestMutation = { __typename?: 'Mutation', cancelRequest: { __typename?: 'CancelRequestResult', success: boolean } };
export type CancelResponseMutationVariables = Exact<{
requestID: Scalars['ID'];
}>;
export type CancelResponseMutation = { __typename?: 'Mutation', cancelResponse: { __typename?: 'CancelResponseResult', success: boolean } };
export type GetInterceptedRequestQueryVariables = Exact<{ export type GetInterceptedRequestQueryVariables = Exact<{
id: Scalars['ID']; id: Scalars['ID'];
}>; }>;
export type GetInterceptedRequestQuery = { __typename?: 'Query', interceptedRequest?: { __typename?: 'HttpRequest', id: string, url: any, method: HttpMethod, proto: HttpProtocol, body?: string | null, headers: Array<{ __typename?: 'HttpHeader', key: string, value: string }> } | null }; export type GetInterceptedRequestQuery = { __typename?: 'Query', interceptedRequest?: { __typename?: 'HttpRequest', id: string, url: any, method: HttpMethod, proto: HttpProtocol, body?: string | null, headers: Array<{ __typename?: 'HttpHeader', key: string, value: string }>, response?: { __typename?: 'HttpResponse', id: string, proto: HttpProtocol, statusCode: number, statusReason: string, body?: string | null, headers: Array<{ __typename?: 'HttpHeader', key: string, value: string }> } | null } | null };
export type ModifyRequestMutationVariables = Exact<{ export type ModifyRequestMutationVariables = Exact<{
request: ModifyRequestInput; request: ModifyRequestInput;
@ -340,6 +391,13 @@ export type ModifyRequestMutationVariables = Exact<{
export type ModifyRequestMutation = { __typename?: 'Mutation', modifyRequest: { __typename?: 'ModifyRequestResult', success: boolean } }; export type ModifyRequestMutation = { __typename?: 'Mutation', modifyRequest: { __typename?: 'ModifyRequestResult', success: boolean } };
export type ModifyResponseMutationVariables = Exact<{
response: ModifyResponseInput;
}>;
export type ModifyResponseMutation = { __typename?: 'Mutation', modifyResponse: { __typename?: 'ModifyResponseResult', success: boolean } };
export type ActiveProjectQueryVariables = Exact<{ [key: string]: never; }>; export type ActiveProjectQueryVariables = Exact<{ [key: string]: never; }>;
@ -460,7 +518,7 @@ export type UpdateInterceptSettingsMutation = { __typename?: 'Mutation', updateI
export type GetInterceptedRequestsQueryVariables = Exact<{ [key: string]: never; }>; export type GetInterceptedRequestsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetInterceptedRequestsQuery = { __typename?: 'Query', interceptedRequests: Array<{ __typename?: 'HttpRequest', id: string, url: any, method: HttpMethod }> }; export type GetInterceptedRequestsQuery = { __typename?: 'Query', interceptedRequests: Array<{ __typename?: 'HttpRequest', id: string, url: any, method: HttpMethod, response?: { __typename?: 'HttpResponse', statusCode: number, statusReason: string } | null }> };
export const CancelRequestDocument = gql` export const CancelRequestDocument = gql`
@ -496,6 +554,39 @@ export function useCancelRequestMutation(baseOptions?: Apollo.MutationHookOption
export type CancelRequestMutationHookResult = ReturnType<typeof useCancelRequestMutation>; export type CancelRequestMutationHookResult = ReturnType<typeof useCancelRequestMutation>;
export type CancelRequestMutationResult = Apollo.MutationResult<CancelRequestMutation>; export type CancelRequestMutationResult = Apollo.MutationResult<CancelRequestMutation>;
export type CancelRequestMutationOptions = Apollo.BaseMutationOptions<CancelRequestMutation, CancelRequestMutationVariables>; export type CancelRequestMutationOptions = Apollo.BaseMutationOptions<CancelRequestMutation, CancelRequestMutationVariables>;
export const CancelResponseDocument = gql`
mutation CancelResponse($requestID: ID!) {
cancelResponse(requestID: $requestID) {
success
}
}
`;
export type CancelResponseMutationFn = Apollo.MutationFunction<CancelResponseMutation, CancelResponseMutationVariables>;
/**
* __useCancelResponseMutation__
*
* To run a mutation, you first call `useCancelResponseMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCancelResponseMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [cancelResponseMutation, { data, loading, error }] = useCancelResponseMutation({
* variables: {
* requestID: // value for 'requestID'
* },
* });
*/
export function useCancelResponseMutation(baseOptions?: Apollo.MutationHookOptions<CancelResponseMutation, CancelResponseMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CancelResponseMutation, CancelResponseMutationVariables>(CancelResponseDocument, options);
}
export type CancelResponseMutationHookResult = ReturnType<typeof useCancelResponseMutation>;
export type CancelResponseMutationResult = Apollo.MutationResult<CancelResponseMutation>;
export type CancelResponseMutationOptions = Apollo.BaseMutationOptions<CancelResponseMutation, CancelResponseMutationVariables>;
export const GetInterceptedRequestDocument = gql` export const GetInterceptedRequestDocument = gql`
query GetInterceptedRequest($id: ID!) { query GetInterceptedRequest($id: ID!) {
interceptedRequest(id: $id) { interceptedRequest(id: $id) {
@ -508,6 +599,17 @@ export const GetInterceptedRequestDocument = gql`
value value
} }
body body
response {
id
proto
statusCode
statusReason
headers {
key
value
}
body
}
} }
} }
`; `;
@ -572,6 +674,39 @@ export function useModifyRequestMutation(baseOptions?: Apollo.MutationHookOption
export type ModifyRequestMutationHookResult = ReturnType<typeof useModifyRequestMutation>; export type ModifyRequestMutationHookResult = ReturnType<typeof useModifyRequestMutation>;
export type ModifyRequestMutationResult = Apollo.MutationResult<ModifyRequestMutation>; export type ModifyRequestMutationResult = Apollo.MutationResult<ModifyRequestMutation>;
export type ModifyRequestMutationOptions = Apollo.BaseMutationOptions<ModifyRequestMutation, ModifyRequestMutationVariables>; export type ModifyRequestMutationOptions = Apollo.BaseMutationOptions<ModifyRequestMutation, ModifyRequestMutationVariables>;
export const ModifyResponseDocument = gql`
mutation ModifyResponse($response: ModifyResponseInput!) {
modifyResponse(response: $response) {
success
}
}
`;
export type ModifyResponseMutationFn = Apollo.MutationFunction<ModifyResponseMutation, ModifyResponseMutationVariables>;
/**
* __useModifyResponseMutation__
*
* To run a mutation, you first call `useModifyResponseMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useModifyResponseMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [modifyResponseMutation, { data, loading, error }] = useModifyResponseMutation({
* variables: {
* response: // value for 'response'
* },
* });
*/
export function useModifyResponseMutation(baseOptions?: Apollo.MutationHookOptions<ModifyResponseMutation, ModifyResponseMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<ModifyResponseMutation, ModifyResponseMutationVariables>(ModifyResponseDocument, options);
}
export type ModifyResponseMutationHookResult = ReturnType<typeof useModifyResponseMutation>;
export type ModifyResponseMutationResult = Apollo.MutationResult<ModifyResponseMutation>;
export type ModifyResponseMutationOptions = Apollo.BaseMutationOptions<ModifyResponseMutation, ModifyResponseMutationVariables>;
export const ActiveProjectDocument = gql` export const ActiveProjectDocument = gql`
query ActiveProject { query ActiveProject {
activeProject { activeProject {
@ -1283,6 +1418,10 @@ export const GetInterceptedRequestsDocument = gql`
id id
url url
method method
response {
statusCode
statusReason
}
} }
} }
`; `;

View File

@ -3,5 +3,9 @@ query GetInterceptedRequests {
id id
url url
method method
response {
statusCode
statusReason
}
} }
} }

View File

@ -208,6 +208,7 @@ func (cmd *HettyCommand) Exec(ctx context.Context, _ []string) error {
proxy.UseRequestModifier(reqLogService.RequestModifier) proxy.UseRequestModifier(reqLogService.RequestModifier)
proxy.UseResponseModifier(reqLogService.ResponseModifier) proxy.UseResponseModifier(reqLogService.ResponseModifier)
proxy.UseRequestModifier(interceptService.RequestModifier) proxy.UseRequestModifier(interceptService.RequestModifier)
proxy.UseResponseModifier(interceptService.ResponseModifier)
fsSub, err := fs.Sub(adminContent, "admin") fsSub, err := fs.Sub(adminContent, "admin")
if err != nil { if err != nil {

View File

@ -49,6 +49,10 @@ type ComplexityRoot struct {
Success func(childComplexity int) int Success func(childComplexity int) int
} }
CancelResponseResult struct {
Success func(childComplexity int) int
}
ClearHTTPRequestLogResult struct { ClearHTTPRequestLogResult struct {
Success func(childComplexity int) int Success func(childComplexity int) int
} }
@ -76,6 +80,7 @@ type ComplexityRoot struct {
ID func(childComplexity int) int ID func(childComplexity int) int
Method func(childComplexity int) int Method func(childComplexity int) int
Proto func(childComplexity int) int Proto func(childComplexity int) int
Response func(childComplexity int) int
URL func(childComplexity int) int URL func(childComplexity int) int
} }
@ -95,6 +100,15 @@ type ComplexityRoot struct {
SearchExpression func(childComplexity int) int SearchExpression func(childComplexity int) int
} }
HTTPResponse struct {
Body func(childComplexity int) int
Headers func(childComplexity int) int
ID func(childComplexity int) int
Proto func(childComplexity int) int
StatusCode func(childComplexity int) int
StatusReason func(childComplexity int) int
}
HTTPResponseLog struct { HTTPResponseLog struct {
Body func(childComplexity int) int Body func(childComplexity int) int
Headers func(childComplexity int) int Headers func(childComplexity int) int
@ -113,8 +127,13 @@ type ComplexityRoot struct {
Success func(childComplexity int) int Success func(childComplexity int) int
} }
ModifyResponseResult struct {
Success func(childComplexity int) int
}
Mutation struct { Mutation struct {
CancelRequest func(childComplexity int, id ulid.ULID) int CancelRequest func(childComplexity int, id ulid.ULID) int
CancelResponse func(childComplexity int, requestID ulid.ULID) int
ClearHTTPRequestLog func(childComplexity int) int ClearHTTPRequestLog func(childComplexity int) int
CloseProject func(childComplexity int) int CloseProject func(childComplexity int) int
CreateOrUpdateSenderRequest func(childComplexity int, request SenderRequestInput) int CreateOrUpdateSenderRequest func(childComplexity int, request SenderRequestInput) int
@ -123,6 +142,7 @@ type ComplexityRoot struct {
DeleteProject func(childComplexity int, id ulid.ULID) int DeleteProject func(childComplexity int, id ulid.ULID) int
DeleteSenderRequests func(childComplexity int) int DeleteSenderRequests func(childComplexity int) int
ModifyRequest func(childComplexity int, request ModifyRequestInput) int ModifyRequest func(childComplexity int, request ModifyRequestInput) int
ModifyResponse func(childComplexity int, response ModifyResponseInput) int
OpenProject func(childComplexity int, id ulid.ULID) int OpenProject func(childComplexity int, id ulid.ULID) int
SendRequest func(childComplexity int, id ulid.ULID) int SendRequest func(childComplexity int, id ulid.ULID) int
SetHTTPRequestLogFilter func(childComplexity int, filter *HTTPRequestLogFilterInput) int SetHTTPRequestLogFilter func(childComplexity int, filter *HTTPRequestLogFilterInput) int
@ -199,6 +219,8 @@ type MutationResolver interface {
DeleteSenderRequests(ctx context.Context) (*DeleteSenderRequestsResult, error) DeleteSenderRequests(ctx context.Context) (*DeleteSenderRequestsResult, error)
ModifyRequest(ctx context.Context, request ModifyRequestInput) (*ModifyRequestResult, error) ModifyRequest(ctx context.Context, request ModifyRequestInput) (*ModifyRequestResult, error)
CancelRequest(ctx context.Context, id ulid.ULID) (*CancelRequestResult, error) CancelRequest(ctx context.Context, id ulid.ULID) (*CancelRequestResult, error)
ModifyResponse(ctx context.Context, response ModifyResponseInput) (*ModifyResponseResult, error)
CancelResponse(ctx context.Context, requestID ulid.ULID) (*CancelResponseResult, error)
UpdateInterceptSettings(ctx context.Context, input UpdateInterceptSettingsInput) (*InterceptSettings, error) UpdateInterceptSettings(ctx context.Context, input UpdateInterceptSettingsInput) (*InterceptSettings, error)
} }
type QueryResolver interface { type QueryResolver interface {
@ -236,6 +258,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.CancelRequestResult.Success(childComplexity), true return e.complexity.CancelRequestResult.Success(childComplexity), true
case "CancelResponseResult.success":
if e.complexity.CancelResponseResult.Success == nil {
break
}
return e.complexity.CancelResponseResult.Success(childComplexity), true
case "ClearHTTPRequestLogResult.success": case "ClearHTTPRequestLogResult.success":
if e.complexity.ClearHTTPRequestLogResult.Success == nil { if e.complexity.ClearHTTPRequestLogResult.Success == nil {
break break
@ -313,6 +342,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.HTTPRequest.Proto(childComplexity), true return e.complexity.HTTPRequest.Proto(childComplexity), true
case "HttpRequest.response":
if e.complexity.HTTPRequest.Response == nil {
break
}
return e.complexity.HTTPRequest.Response(childComplexity), true
case "HttpRequest.url": case "HttpRequest.url":
if e.complexity.HTTPRequest.URL == nil { if e.complexity.HTTPRequest.URL == nil {
break break
@ -390,6 +426,48 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.HTTPRequestLogFilter.SearchExpression(childComplexity), true return e.complexity.HTTPRequestLogFilter.SearchExpression(childComplexity), true
case "HttpResponse.body":
if e.complexity.HTTPResponse.Body == nil {
break
}
return e.complexity.HTTPResponse.Body(childComplexity), true
case "HttpResponse.headers":
if e.complexity.HTTPResponse.Headers == nil {
break
}
return e.complexity.HTTPResponse.Headers(childComplexity), true
case "HttpResponse.id":
if e.complexity.HTTPResponse.ID == nil {
break
}
return e.complexity.HTTPResponse.ID(childComplexity), true
case "HttpResponse.proto":
if e.complexity.HTTPResponse.Proto == nil {
break
}
return e.complexity.HTTPResponse.Proto(childComplexity), true
case "HttpResponse.statusCode":
if e.complexity.HTTPResponse.StatusCode == nil {
break
}
return e.complexity.HTTPResponse.StatusCode(childComplexity), true
case "HttpResponse.statusReason":
if e.complexity.HTTPResponse.StatusReason == nil {
break
}
return e.complexity.HTTPResponse.StatusReason(childComplexity), true
case "HttpResponseLog.body": case "HttpResponseLog.body":
if e.complexity.HTTPResponseLog.Body == nil { if e.complexity.HTTPResponseLog.Body == nil {
break break
@ -453,6 +531,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ModifyRequestResult.Success(childComplexity), true return e.complexity.ModifyRequestResult.Success(childComplexity), true
case "ModifyResponseResult.success":
if e.complexity.ModifyResponseResult.Success == nil {
break
}
return e.complexity.ModifyResponseResult.Success(childComplexity), true
case "Mutation.cancelRequest": case "Mutation.cancelRequest":
if e.complexity.Mutation.CancelRequest == nil { if e.complexity.Mutation.CancelRequest == nil {
break break
@ -465,6 +550,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.CancelRequest(childComplexity, args["id"].(ulid.ULID)), true return e.complexity.Mutation.CancelRequest(childComplexity, args["id"].(ulid.ULID)), true
case "Mutation.cancelResponse":
if e.complexity.Mutation.CancelResponse == nil {
break
}
args, err := ec.field_Mutation_cancelResponse_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.CancelResponse(childComplexity, args["requestID"].(ulid.ULID)), true
case "Mutation.clearHTTPRequestLog": case "Mutation.clearHTTPRequestLog":
if e.complexity.Mutation.ClearHTTPRequestLog == nil { if e.complexity.Mutation.ClearHTTPRequestLog == nil {
break break
@ -546,6 +643,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.ModifyRequest(childComplexity, args["request"].(ModifyRequestInput)), true return e.complexity.Mutation.ModifyRequest(childComplexity, args["request"].(ModifyRequestInput)), true
case "Mutation.modifyResponse":
if e.complexity.Mutation.ModifyResponse == nil {
break
}
args, err := ec.field_Mutation_modifyResponse_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.ModifyResponse(childComplexity, args["response"].(ModifyResponseInput)), true
case "Mutation.openProject": case "Mutation.openProject":
if e.complexity.Mutation.OpenProject == nil { if e.complexity.Mutation.OpenProject == nil {
break break
@ -1044,6 +1153,19 @@ type HttpRequest {
proto: HttpProtocol! proto: HttpProtocol!
headers: [HttpHeader!]! headers: [HttpHeader!]!
body: String body: String
response: HttpResponse
}
type HttpResponse {
"""
Will be the same ID as its related request ID.
"""
id: ID!
proto: HttpProtocol!
statusCode: Int!
statusReason: String!
body: String
headers: [HttpHeader!]!
} }
input ModifyRequestInput { input ModifyRequestInput {
@ -1053,6 +1175,7 @@ input ModifyRequestInput {
proto: HttpProtocol! proto: HttpProtocol!
headers: [HttpHeaderInput!] headers: [HttpHeaderInput!]
body: String body: String
modifyResponse: Boolean
} }
type ModifyRequestResult { type ModifyRequestResult {
@ -1063,6 +1186,23 @@ type CancelRequestResult {
success: Boolean! success: Boolean!
} }
input ModifyResponseInput {
requestID: ID!
proto: HttpProtocol!
headers: [HttpHeaderInput!]
body: String
statusCode: Int!
statusReason: String!
}
type ModifyResponseResult {
success: Boolean!
}
type CancelResponseResult {
success: Boolean!
}
input UpdateInterceptSettingsInput { input UpdateInterceptSettingsInput {
enabled: Boolean! enabled: Boolean!
requestFilter: String requestFilter: String
@ -1103,6 +1243,8 @@ type Mutation {
deleteSenderRequests: DeleteSenderRequestsResult! deleteSenderRequests: DeleteSenderRequestsResult!
modifyRequest(request: ModifyRequestInput!): ModifyRequestResult! modifyRequest(request: ModifyRequestInput!): ModifyRequestResult!
cancelRequest(id: ID!): CancelRequestResult! cancelRequest(id: ID!): CancelRequestResult!
modifyResponse(response: ModifyResponseInput!): ModifyResponseResult!
cancelResponse(requestID: ID!): CancelResponseResult!
updateInterceptSettings( updateInterceptSettings(
input: UpdateInterceptSettingsInput! input: UpdateInterceptSettingsInput!
): InterceptSettings! ): InterceptSettings!
@ -1152,6 +1294,21 @@ func (ec *executionContext) field_Mutation_cancelRequest_args(ctx context.Contex
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_cancelResponse_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 ulid.ULID
if tmp, ok := rawArgs["requestID"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestID"))
arg0, err = ec.unmarshalNID2githubᚗcomᚋoklogᚋulidᚐULID(ctx, tmp)
if err != nil {
return nil, err
}
}
args["requestID"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_createOrUpdateSenderRequest_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_createOrUpdateSenderRequest_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -1227,6 +1384,21 @@ func (ec *executionContext) field_Mutation_modifyRequest_args(ctx context.Contex
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_modifyResponse_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 ModifyResponseInput
if tmp, ok := rawArgs["response"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("response"))
arg0, err = ec.unmarshalNModifyResponseInput2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐModifyResponseInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["response"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_openProject_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_openProject_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@ -1450,6 +1622,41 @@ func (ec *executionContext) _CancelRequestResult_success(ctx context.Context, fi
return ec.marshalNBoolean2bool(ctx, field.Selections, res) return ec.marshalNBoolean2bool(ctx, field.Selections, res)
} }
func (ec *executionContext) _CancelResponseResult_success(ctx context.Context, field graphql.CollectedField, obj *CancelResponseResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "CancelResponseResult",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Success, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _ClearHTTPRequestLogResult_success(ctx context.Context, field graphql.CollectedField, obj *ClearHTTPRequestLogResult) (ret graphql.Marshaler) { func (ec *executionContext) _ClearHTTPRequestLogResult_success(ctx context.Context, field graphql.CollectedField, obj *ClearHTTPRequestLogResult) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -1867,6 +2074,38 @@ func (ec *executionContext) _HttpRequest_body(ctx context.Context, field graphql
return ec.marshalOString2ᚖstring(ctx, field.Selections, res) return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
} }
func (ec *executionContext) _HttpRequest_response(ctx context.Context, field graphql.CollectedField, obj *HTTPRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpRequest",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Response, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*HTTPResponse)
fc.Result = res
return ec.marshalOHttpResponse2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpRequestLog_id(ctx context.Context, field graphql.CollectedField, obj *HTTPRequestLog) (ret graphql.Marshaler) { func (ec *executionContext) _HttpRequestLog_id(ctx context.Context, field graphql.CollectedField, obj *HTTPRequestLog) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -2208,6 +2447,213 @@ func (ec *executionContext) _HttpRequestLogFilter_searchExpression(ctx context.C
return ec.marshalOString2ᚖstring(ctx, field.Selections, res) return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
} }
func (ec *executionContext) _HttpResponse_id(ctx context.Context, field graphql.CollectedField, obj *HTTPResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ID, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(ulid.ULID)
fc.Result = res
return ec.marshalNID2githubᚗcomᚋoklogᚋulidᚐULID(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpResponse_proto(ctx context.Context, field graphql.CollectedField, obj *HTTPResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Proto, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(HTTPProtocol)
fc.Result = res
return ec.marshalNHttpProtocol2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPProtocol(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpResponse_statusCode(ctx context.Context, field graphql.CollectedField, obj *HTTPResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.StatusCode, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
fc.Result = res
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpResponse_statusReason(ctx context.Context, field graphql.CollectedField, obj *HTTPResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.StatusReason, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpResponse_body(ctx context.Context, field graphql.CollectedField, obj *HTTPResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Body, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpResponse_headers(ctx context.Context, field graphql.CollectedField, obj *HTTPResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "HttpResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Headers, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]HTTPHeader)
fc.Result = res
return ec.marshalNHttpHeader2ᚕgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPHeaderᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _HttpResponseLog_id(ctx context.Context, field graphql.CollectedField, obj *HTTPResponseLog) (ret graphql.Marshaler) { func (ec *executionContext) _HttpResponseLog_id(ctx context.Context, field graphql.CollectedField, obj *HTTPResponseLog) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -2517,6 +2963,41 @@ func (ec *executionContext) _ModifyRequestResult_success(ctx context.Context, fi
return ec.marshalNBoolean2bool(ctx, field.Selections, res) return ec.marshalNBoolean2bool(ctx, field.Selections, res)
} }
func (ec *executionContext) _ModifyResponseResult_success(ctx context.Context, field graphql.CollectedField, obj *ModifyResponseResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ModifyResponseResult",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Success, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_createProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_createProject(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -3072,6 +3553,90 @@ func (ec *executionContext) _Mutation_cancelRequest(ctx context.Context, field g
return ec.marshalNCancelRequestResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCancelRequestResult(ctx, field.Selections, res) return ec.marshalNCancelRequestResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCancelRequestResult(ctx, field.Selections, res)
} }
func (ec *executionContext) _Mutation_modifyResponse(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_modifyResponse_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().ModifyResponse(rctx, args["response"].(ModifyResponseInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*ModifyResponseResult)
fc.Result = res
return ec.marshalNModifyResponseResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐModifyResponseResult(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_cancelResponse(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_cancelResponse_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().CancelResponse(rctx, args["requestID"].(ulid.ULID))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*CancelResponseResult)
fc.Result = res
return ec.marshalNCancelResponseResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCancelResponseResult(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_updateInterceptSettings(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_updateInterceptSettings(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -5487,6 +6052,77 @@ func (ec *executionContext) unmarshalInputModifyRequestInput(ctx context.Context
if err != nil { if err != nil {
return it, err return it, err
} }
case "modifyResponse":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("modifyResponse"))
it.ModifyResponse, err = ec.unmarshalOBoolean2ᚖbool(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputModifyResponseInput(ctx context.Context, obj interface{}) (ModifyResponseInput, error) {
var it ModifyResponseInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
for k, v := range asMap {
switch k {
case "requestID":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("requestID"))
it.RequestID, err = ec.unmarshalNID2githubᚗcomᚋoklogᚋulidᚐULID(ctx, v)
if err != nil {
return it, err
}
case "proto":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("proto"))
it.Proto, err = ec.unmarshalNHttpProtocol2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPProtocol(ctx, v)
if err != nil {
return it, err
}
case "headers":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("headers"))
it.Headers, err = ec.unmarshalOHttpHeaderInput2ᚕgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPHeaderInputᚄ(ctx, v)
if err != nil {
return it, err
}
case "body":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("body"))
it.Body, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "statusCode":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("statusCode"))
it.StatusCode, err = ec.unmarshalNInt2int(ctx, v)
if err != nil {
return it, err
}
case "statusReason":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("statusReason"))
it.StatusReason, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
} }
} }
@ -5723,6 +6359,33 @@ func (ec *executionContext) _CancelRequestResult(ctx context.Context, sel ast.Se
return out return out
} }
var cancelResponseResultImplementors = []string{"CancelResponseResult"}
func (ec *executionContext) _CancelResponseResult(ctx context.Context, sel ast.SelectionSet, obj *CancelResponseResult) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, cancelResponseResultImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("CancelResponseResult")
case "success":
out.Values[i] = ec._CancelResponseResult_success(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var clearHTTPRequestLogResultImplementors = []string{"ClearHTTPRequestLogResult"} var clearHTTPRequestLogResultImplementors = []string{"ClearHTTPRequestLogResult"}
func (ec *executionContext) _ClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, obj *ClearHTTPRequestLogResult) graphql.Marshaler { func (ec *executionContext) _ClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, obj *ClearHTTPRequestLogResult) graphql.Marshaler {
@ -5901,6 +6564,8 @@ func (ec *executionContext) _HttpRequest(ctx context.Context, sel ast.SelectionS
} }
case "body": case "body":
out.Values[i] = ec._HttpRequest_body(ctx, field, obj) out.Values[i] = ec._HttpRequest_body(ctx, field, obj)
case "response":
out.Values[i] = ec._HttpRequest_response(ctx, field, obj)
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@ -5997,6 +6662,55 @@ func (ec *executionContext) _HttpRequestLogFilter(ctx context.Context, sel ast.S
return out return out
} }
var httpResponseImplementors = []string{"HttpResponse"}
func (ec *executionContext) _HttpResponse(ctx context.Context, sel ast.SelectionSet, obj *HTTPResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, httpResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("HttpResponse")
case "id":
out.Values[i] = ec._HttpResponse_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "proto":
out.Values[i] = ec._HttpResponse_proto(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "statusCode":
out.Values[i] = ec._HttpResponse_statusCode(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "statusReason":
out.Values[i] = ec._HttpResponse_statusReason(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "body":
out.Values[i] = ec._HttpResponse_body(ctx, field, obj)
case "headers":
out.Values[i] = ec._HttpResponse_headers(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var httpResponseLogImplementors = []string{"HttpResponseLog"} var httpResponseLogImplementors = []string{"HttpResponseLog"}
func (ec *executionContext) _HttpResponseLog(ctx context.Context, sel ast.SelectionSet, obj *HTTPResponseLog) graphql.Marshaler { func (ec *executionContext) _HttpResponseLog(ctx context.Context, sel ast.SelectionSet, obj *HTTPResponseLog) graphql.Marshaler {
@ -6102,6 +6816,33 @@ func (ec *executionContext) _ModifyRequestResult(ctx context.Context, sel ast.Se
return out return out
} }
var modifyResponseResultImplementors = []string{"ModifyResponseResult"}
func (ec *executionContext) _ModifyResponseResult(ctx context.Context, sel ast.SelectionSet, obj *ModifyResponseResult) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, modifyResponseResultImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ModifyResponseResult")
case "success":
out.Values[i] = ec._ModifyResponseResult_success(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var mutationImplementors = []string{"Mutation"} var mutationImplementors = []string{"Mutation"}
func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
@ -6175,6 +6916,16 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "modifyResponse":
out.Values[i] = ec._Mutation_modifyResponse(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "cancelResponse":
out.Values[i] = ec._Mutation_cancelResponse(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "updateInterceptSettings": case "updateInterceptSettings":
out.Values[i] = ec._Mutation_updateInterceptSettings(ctx, field) out.Values[i] = ec._Mutation_updateInterceptSettings(ctx, field)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@ -6832,6 +7583,20 @@ func (ec *executionContext) marshalNCancelRequestResult2ᚖgithubᚗcomᚋdstoti
return ec._CancelRequestResult(ctx, sel, v) return ec._CancelRequestResult(ctx, sel, v)
} }
func (ec *executionContext) marshalNCancelResponseResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCancelResponseResult(ctx context.Context, sel ast.SelectionSet, v CancelResponseResult) graphql.Marshaler {
return ec._CancelResponseResult(ctx, sel, &v)
}
func (ec *executionContext) marshalNCancelResponseResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCancelResponseResult(ctx context.Context, sel ast.SelectionSet, v *CancelResponseResult) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._CancelResponseResult(ctx, sel, v)
}
func (ec *executionContext) marshalNClearHTTPRequestLogResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, v ClearHTTPRequestLogResult) graphql.Marshaler { func (ec *executionContext) marshalNClearHTTPRequestLogResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, v ClearHTTPRequestLogResult) graphql.Marshaler {
return ec._ClearHTTPRequestLogResult(ctx, sel, &v) return ec._ClearHTTPRequestLogResult(ctx, sel, &v)
} }
@ -7120,6 +7885,25 @@ func (ec *executionContext) marshalNModifyRequestResult2ᚖgithubᚗcomᚋdstoti
return ec._ModifyRequestResult(ctx, sel, v) return ec._ModifyRequestResult(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNModifyResponseInput2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐModifyResponseInput(ctx context.Context, v interface{}) (ModifyResponseInput, error) {
res, err := ec.unmarshalInputModifyResponseInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNModifyResponseResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐModifyResponseResult(ctx context.Context, sel ast.SelectionSet, v ModifyResponseResult) graphql.Marshaler {
return ec._ModifyResponseResult(ctx, sel, &v)
}
func (ec *executionContext) marshalNModifyResponseResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐModifyResponseResult(ctx context.Context, sel ast.SelectionSet, v *ModifyResponseResult) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ModifyResponseResult(ctx, sel, v)
}
func (ec *executionContext) marshalNProject2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProject(ctx context.Context, sel ast.SelectionSet, v Project) graphql.Marshaler { func (ec *executionContext) marshalNProject2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐProject(ctx context.Context, sel ast.SelectionSet, v Project) graphql.Marshaler {
return ec._Project(ctx, sel, &v) return ec._Project(ctx, sel, &v)
} }
@ -7784,6 +8568,13 @@ func (ec *executionContext) unmarshalOHttpRequestLogFilterInput2ᚖgithubᚗcom
return &res, graphql.ErrorOnPath(ctx, err) return &res, graphql.ErrorOnPath(ctx, err)
} }
func (ec *executionContext) marshalOHttpResponse2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPResponse(ctx context.Context, sel ast.SelectionSet, v *HTTPResponse) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._HttpResponse(ctx, sel, v)
}
func (ec *executionContext) marshalOHttpResponseLog2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPResponseLog(ctx context.Context, sel ast.SelectionSet, v *HTTPResponseLog) graphql.Marshaler { func (ec *executionContext) marshalOHttpResponseLog2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐHTTPResponseLog(ctx context.Context, sel ast.SelectionSet, v *HTTPResponseLog) graphql.Marshaler {
if v == nil { if v == nil {
return graphql.Null return graphql.Null

View File

@ -16,6 +16,10 @@ type CancelRequestResult struct {
Success bool `json:"success"` Success bool `json:"success"`
} }
type CancelResponseResult struct {
Success bool `json:"success"`
}
type ClearHTTPRequestLogResult struct { type ClearHTTPRequestLogResult struct {
Success bool `json:"success"` Success bool `json:"success"`
} }
@ -49,6 +53,7 @@ type HTTPRequest struct {
Proto HTTPProtocol `json:"proto"` Proto HTTPProtocol `json:"proto"`
Headers []HTTPHeader `json:"headers"` Headers []HTTPHeader `json:"headers"`
Body *string `json:"body"` Body *string `json:"body"`
Response *HTTPResponse `json:"response"`
} }
type HTTPRequestLog struct { type HTTPRequestLog struct {
@ -72,6 +77,16 @@ type HTTPRequestLogFilterInput struct {
SearchExpression *string `json:"searchExpression"` SearchExpression *string `json:"searchExpression"`
} }
type HTTPResponse struct {
// Will be the same ID as its related request ID.
ID ulid.ULID `json:"id"`
Proto HTTPProtocol `json:"proto"`
StatusCode int `json:"statusCode"`
StatusReason string `json:"statusReason"`
Body *string `json:"body"`
Headers []HTTPHeader `json:"headers"`
}
type HTTPResponseLog struct { type HTTPResponseLog struct {
// Will be the same ID as its related request ID. // Will be the same ID as its related request ID.
ID ulid.ULID `json:"id"` ID ulid.ULID `json:"id"`
@ -94,12 +109,26 @@ type ModifyRequestInput struct {
Proto HTTPProtocol `json:"proto"` Proto HTTPProtocol `json:"proto"`
Headers []HTTPHeaderInput `json:"headers"` Headers []HTTPHeaderInput `json:"headers"`
Body *string `json:"body"` Body *string `json:"body"`
ModifyResponse *bool `json:"modifyResponse"`
} }
type ModifyRequestResult struct { type ModifyRequestResult struct {
Success bool `json:"success"` Success bool `json:"success"`
} }
type ModifyResponseInput struct {
RequestID ulid.ULID `json:"requestID"`
Proto HTTPProtocol `json:"proto"`
Headers []HTTPHeaderInput `json:"headers"`
Body *string `json:"body"`
StatusCode int `json:"statusCode"`
StatusReason string `json:"statusReason"`
}
type ModifyResponseResult struct {
Success bool `json:"success"`
}
type Project struct { type Project struct {
ID ulid.ULID `json:"id"` ID ulid.ULID `json:"id"`
Name string `json:"name"` Name string `json:"name"`

View File

@ -7,6 +7,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"regexp" "regexp"
@ -515,36 +516,35 @@ func (r *mutationResolver) DeleteSenderRequests(ctx context.Context) (*DeleteSen
return &DeleteSenderRequestsResult{true}, nil return &DeleteSenderRequestsResult{true}, nil
} }
func (r *queryResolver) InterceptedRequests(ctx context.Context) ([]HTTPRequest, error) { func (r *queryResolver) InterceptedRequests(ctx context.Context) (httpReqs []HTTPRequest, err error) {
reqs := r.InterceptService.Requests() items := r.InterceptService.Items()
httpReqs := make([]HTTPRequest, len(reqs))
for i, req := range reqs { for _, item := range items {
req, err := parseHTTPRequest(req) req, err := parseInterceptItem(item)
if err != nil { if err != nil {
return nil, err return nil, err
} }
httpReqs[i] = req httpReqs = append(httpReqs, req)
} }
return httpReqs, nil return httpReqs, nil
} }
func (r *queryResolver) InterceptedRequest(ctx context.Context, id ulid.ULID) (*HTTPRequest, error) { func (r *queryResolver) InterceptedRequest(ctx context.Context, id ulid.ULID) (*HTTPRequest, error) {
req, err := r.InterceptService.RequestByID(id) item, err := r.InterceptService.ItemByID(id)
if errors.Is(err, intercept.ErrRequestNotFound) { if errors.Is(err, intercept.ErrRequestNotFound) {
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
return nil, fmt.Errorf("could not get request by ID: %w", err) return nil, fmt.Errorf("could not get request by ID: %w", err)
} }
httpReq, err := parseHTTPRequest(req) req, err := parseInterceptItem(item)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &httpReq, nil return &req, nil
} }
func (r *mutationResolver) ModifyRequest(ctx context.Context, input ModifyRequestInput) (*ModifyRequestResult, error) { func (r *mutationResolver) ModifyRequest(ctx context.Context, input ModifyRequestInput) (*ModifyRequestResult, error) {
@ -563,7 +563,7 @@ func (r *mutationResolver) ModifyRequest(ctx context.Context, input ModifyReques
req.Header.Add(header.Key, header.Value) req.Header.Add(header.Key, header.Value)
} }
err = r.InterceptService.ModifyRequest(input.ID, req) err = r.InterceptService.ModifyRequest(input.ID, req, input.ModifyResponse)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not modify http request: %w", err) return nil, fmt.Errorf("could not modify http request: %w", err)
} }
@ -580,6 +580,47 @@ func (r *mutationResolver) CancelRequest(ctx context.Context, id ulid.ULID) (*Ca
return &CancelRequestResult{Success: true}, nil return &CancelRequestResult{Success: true}, nil
} }
func (r *mutationResolver) ModifyResponse(
ctx context.Context,
input ModifyResponseInput,
) (*ModifyResponseResult, error) {
res := &http.Response{
Header: make(http.Header),
Status: fmt.Sprintf("%v %v", input.StatusCode, input.StatusReason),
StatusCode: input.StatusCode,
Proto: revHTTPProtocolMap[input.Proto],
}
var ok bool
if res.ProtoMajor, res.ProtoMinor, ok = http.ParseHTTPVersion(res.Proto); !ok {
return nil, fmt.Errorf("malformed HTTP version: %q", res.Proto)
}
if input.Body != nil {
res.Body = io.NopCloser(strings.NewReader(*input.Body))
}
for _, header := range input.Headers {
res.Header.Add(header.Key, header.Value)
}
err := r.InterceptService.ModifyResponse(input.RequestID, res)
if err != nil {
return nil, fmt.Errorf("could not modify http request: %w", err)
}
return &ModifyResponseResult{Success: true}, nil
}
func (r *mutationResolver) CancelResponse(ctx context.Context, requestID ulid.ULID) (*CancelResponseResult, error) {
err := r.InterceptService.CancelResponse(requestID)
if err != nil {
return nil, fmt.Errorf("could not cancel http response: %w", err)
}
return &CancelResponseResult{Success: true}, nil
}
func (r *mutationResolver) UpdateInterceptSettings( func (r *mutationResolver) UpdateInterceptSettings(
ctx context.Context, ctx context.Context,
input UpdateInterceptSettingsInput, input UpdateInterceptSettingsInput,
@ -721,6 +762,79 @@ func parseHTTPRequest(req *http.Request) (HTTPRequest, error) {
return httpReq, nil return httpReq, nil
} }
func parseHTTPResponse(res *http.Response) (HTTPResponse, error) {
resProto := httpProtocolMap[res.Proto]
if !resProto.IsValid() {
return HTTPResponse{}, fmt.Errorf("http response has invalid protocol: %v", res.Proto)
}
id, ok := proxy.RequestIDFromContext(res.Request.Context())
if !ok {
return HTTPResponse{}, errors.New("http response has missing ID")
}
httpRes := HTTPResponse{
ID: id,
Proto: resProto,
StatusCode: res.StatusCode,
}
statusReasonSubs := strings.SplitN(res.Status, " ", 2)
if len(statusReasonSubs) == 2 {
httpRes.StatusReason = statusReasonSubs[1]
}
if res.Header != nil {
httpRes.Headers = make([]HTTPHeader, 0)
for key, values := range res.Header {
for _, value := range values {
httpRes.Headers = append(httpRes.Headers, HTTPHeader{
Key: key,
Value: value,
})
}
}
}
if res.Body != nil {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return HTTPResponse{}, fmt.Errorf("failed to read response body: %w", err)
}
res.Body = ioutil.NopCloser(bytes.NewBuffer(body))
bodyStr := string(body)
httpRes.Body = &bodyStr
}
return httpRes, nil
}
func parseInterceptItem(item intercept.Item) (req HTTPRequest, err error) {
if item.Response != nil {
req, err = parseHTTPRequest(item.Response.Request)
if err != nil {
return HTTPRequest{}, err
}
res, err := parseHTTPResponse(item.Response)
if err != nil {
return HTTPRequest{}, err
}
req.Response = &res
} else if item.Request != nil {
req, err = parseHTTPRequest(item.Request)
if err != nil {
return HTTPRequest{}, err
}
}
return req, nil
}
func parseProject(projSvc proj.Service, p proj.Project) Project { func parseProject(projSvc proj.Service, p proj.Project) Project {
project := Project{ project := Project{
ID: p.ID, ID: p.ID,

View File

@ -128,6 +128,19 @@ type HttpRequest {
proto: HttpProtocol! proto: HttpProtocol!
headers: [HttpHeader!]! headers: [HttpHeader!]!
body: String body: String
response: HttpResponse
}
type HttpResponse {
"""
Will be the same ID as its related request ID.
"""
id: ID!
proto: HttpProtocol!
statusCode: Int!
statusReason: String!
body: String
headers: [HttpHeader!]!
} }
input ModifyRequestInput { input ModifyRequestInput {
@ -137,6 +150,7 @@ input ModifyRequestInput {
proto: HttpProtocol! proto: HttpProtocol!
headers: [HttpHeaderInput!] headers: [HttpHeaderInput!]
body: String body: String
modifyResponse: Boolean
} }
type ModifyRequestResult { type ModifyRequestResult {
@ -147,6 +161,23 @@ type CancelRequestResult {
success: Boolean! success: Boolean!
} }
input ModifyResponseInput {
requestID: ID!
proto: HttpProtocol!
headers: [HttpHeaderInput!]
body: String
statusCode: Int!
statusReason: String!
}
type ModifyResponseResult {
success: Boolean!
}
type CancelResponseResult {
success: Boolean!
}
input UpdateInterceptSettingsInput { input UpdateInterceptSettingsInput {
enabled: Boolean! enabled: Boolean!
requestFilter: String requestFilter: String
@ -187,6 +218,8 @@ type Mutation {
deleteSenderRequests: DeleteSenderRequestsResult! deleteSenderRequests: DeleteSenderRequestsResult!
modifyRequest(request: ModifyRequestInput!): ModifyRequestResult! modifyRequest(request: ModifyRequestInput!): ModifyRequestResult!
cancelRequest(id: ID!): CancelRequestResult! cancelRequest(id: ID!): CancelRequestResult!
modifyResponse(response: ModifyResponseInput!): ModifyResponseResult!
cancelResponse(requestID: ID!): CancelResponseResult!
updateInterceptSettings( updateInterceptSettings(
input: UpdateInterceptSettingsInput! input: UpdateInterceptSettingsInput!
): InterceptSettings! ): InterceptSettings!

View File

@ -13,6 +13,7 @@ import (
"github.com/dstotijn/hetty/pkg/search" "github.com/dstotijn/hetty/pkg/search"
) )
//nolint:unparam
var reqFilterKeyFns = map[string]func(req *http.Request) (string, error){ var reqFilterKeyFns = map[string]func(req *http.Request) (string, error){
"proto": func(req *http.Request) (string, error) { return req.Proto, nil }, "proto": func(req *http.Request) (string, error) { return req.Proto, nil },
"url": func(req *http.Request) (string, error) { "url": func(req *http.Request) (string, error) {

View File

@ -19,8 +19,13 @@ var (
ErrRequestAborted = errors.New("intercept: request was aborted") ErrRequestAborted = errors.New("intercept: request was aborted")
ErrRequestNotFound = errors.New("intercept: request not found") ErrRequestNotFound = errors.New("intercept: request not found")
ErrRequestDone = errors.New("intercept: request is done") ErrRequestDone = errors.New("intercept: request is done")
ErrResponseNotFound = errors.New("intercept: response not found")
) )
type contextKey int
const interceptResponseKey contextKey = 0
// Request represents a server received HTTP request, alongside a channel for sending a modified version of it to the // Request represents a server received HTTP request, alongside a channel for sending a modified version of it to the
// routine that's awaiting it. Also contains a channel for receiving a cancellation signal. // routine that's awaiting it. Also contains a channel for receiving a cancellation signal.
type Request struct { type Request struct {
@ -29,9 +34,24 @@ type Request struct {
done <-chan struct{} done <-chan struct{}
} }
// Response represents an HTTP response from a proxied request, alongside a channel for sending a modified version of it
// to the routine that's awaiting it. Also contains a channel for receiving a cancellation signal.
type Response struct {
res *http.Response
ch chan<- *http.Response
done <-chan struct{}
}
type Item struct {
Request *http.Request
Response *http.Response
}
type Service struct { type Service struct {
mu *sync.RWMutex reqMu *sync.RWMutex
resMu *sync.RWMutex
requests map[ulid.ULID]Request requests map[ulid.ULID]Request
responses map[ulid.ULID]Response
logger log.Logger logger log.Logger
enabled bool enabled bool
reqFilter search.Expression reqFilter search.Expression
@ -48,8 +68,10 @@ type RequestIDs []ulid.ULID
func NewService(cfg Config) *Service { func NewService(cfg Config) *Service {
s := &Service{ s := &Service{
mu: &sync.RWMutex{}, reqMu: &sync.RWMutex{},
resMu: &sync.RWMutex{},
requests: make(map[ulid.ULID]Request), requests: make(map[ulid.ULID]Request),
responses: make(map[ulid.ULID]Response),
logger: cfg.Logger, logger: cfg.Logger,
enabled: cfg.Enabled, enabled: cfg.Enabled,
reqFilter: cfg.RequestFilter, reqFilter: cfg.RequestFilter,
@ -62,13 +84,12 @@ func NewService(cfg Config) *Service {
return s return s
} }
// RequestModifier is a proxy.RequestModifyMiddleware for intercepting HTTP // RequestModifier is a proxy.RequestModifyMiddleware for intercepting HTTP requests.
// requests.
func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc { func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestModifyFunc {
return func(req *http.Request) { return func(req *http.Request) {
// This is a blocking operation, that gets unblocked when either a modified request is returned or an error // This is a blocking operation, that gets unblocked when either a modified request is returned or an error
// (typically `context.Canceled`). // (typically `context.Canceled`).
modifiedReq, err := svc.Intercept(req.Context(), req) modifiedReq, err := svc.InterceptRequest(req.Context(), req)
switch { switch {
case errors.Is(err, ErrRequestAborted): case errors.Is(err, ErrRequestAborted):
@ -86,24 +107,24 @@ func (svc *Service) RequestModifier(next proxy.RequestModifyFunc) proxy.RequestM
svc.logger.Errorw("Failed to intercept request.", svc.logger.Errorw("Failed to intercept request.",
"error", err) "error", err)
default: default:
*req = *modifiedReq.WithContext(req.Context()) *req = *modifiedReq
next(req) next(req)
} }
} }
} }
// Intercept adds an HTTP request to an array of pending intercepted requests, alongside channels used for sending a // InterceptRequest adds an HTTP request to an array of pending intercepted requests, alongside channels used for
// cancellation signal and receiving a modified request. It's safe for concurrent use. // sending a cancellation signal and receiving a modified request. It's safe for concurrent use.
func (svc *Service) Intercept(ctx context.Context, req *http.Request) (*http.Request, error) { func (svc *Service) InterceptRequest(ctx context.Context, req *http.Request) (*http.Request, error) {
reqID, ok := proxy.RequestIDFromContext(ctx) reqID, ok := proxy.RequestIDFromContext(ctx)
if !ok { if !ok {
svc.logger.Errorw("Failed to intercept: request doesn't have an ID.") svc.logger.Errorw("Failed to intercept: context doesn't have an ID.")
return req, nil return req, nil
} }
if !svc.enabled { if !svc.enabled {
// If intercept is disabled, return the incoming request as-is. // If request intercept is disabled, return the incoming request as-is.
svc.logger.Debugw("Bypassed interception: module disabled.") svc.logger.Debugw("Bypassed request interception: feature disabled.")
return req, nil return req, nil
} }
@ -116,7 +137,7 @@ func (svc *Service) Intercept(ctx context.Context, req *http.Request) (*http.Req
} }
if !match { if !match {
svc.logger.Debugw("Bypassed interception: request rules don't match.") svc.logger.Debugw("Bypassed request interception: request rules don't match.")
return req, nil return req, nil
} }
} }
@ -124,20 +145,20 @@ func (svc *Service) Intercept(ctx context.Context, req *http.Request) (*http.Req
ch := make(chan *http.Request) ch := make(chan *http.Request)
done := make(chan struct{}) done := make(chan struct{})
svc.mu.Lock() svc.reqMu.Lock()
svc.requests[reqID] = Request{ svc.requests[reqID] = Request{
req: req, req: req,
ch: ch, ch: ch,
done: done, done: done,
} }
svc.mu.Unlock() svc.reqMu.Unlock()
// Whatever happens next (modified request returned, or a context cancelled error), any blocked channel senders // Whatever happens next (modified request returned, or a context cancelled error), any blocked channel senders
// should be unblocked, and the request should be removed from the requests queue. // should be unblocked, and the request should be removed from the requests queue.
defer func() { defer func() {
close(done) close(done)
svc.mu.Lock() svc.reqMu.Lock()
defer svc.mu.Unlock() defer svc.reqMu.Unlock()
delete(svc.requests, reqID) delete(svc.requests, reqID)
}() }()
@ -155,15 +176,20 @@ func (svc *Service) Intercept(ctx context.Context, req *http.Request) (*http.Req
// ModifyRequest sends a modified HTTP request to the related channel, or returns ErrRequestDone when the request was // ModifyRequest sends a modified HTTP request to the related channel, or returns ErrRequestDone when the request was
// cancelled. It's safe for concurrent use. // cancelled. It's safe for concurrent use.
func (svc *Service) ModifyRequest(reqID ulid.ULID, modReq *http.Request) error { func (svc *Service) ModifyRequest(reqID ulid.ULID, modReq *http.Request, modifyResponse *bool) error {
svc.mu.RLock() svc.reqMu.RLock()
req, ok := svc.requests[reqID] req, ok := svc.requests[reqID]
svc.mu.RUnlock() svc.reqMu.RUnlock()
if !ok { if !ok {
return ErrRequestNotFound return ErrRequestNotFound
} }
*modReq = *modReq.WithContext(req.req.Context())
if modifyResponse != nil {
*modReq = *modReq.WithContext(WithInterceptResponse(modReq.Context(), *modifyResponse))
}
select { select {
case <-req.done: case <-req.done:
return ErrRequestDone return ErrRequestDone
@ -174,12 +200,12 @@ func (svc *Service) ModifyRequest(reqID ulid.ULID, modReq *http.Request) error {
// CancelRequest ensures an intercepted request is dropped. // CancelRequest ensures an intercepted request is dropped.
func (svc *Service) CancelRequest(reqID ulid.ULID) error { func (svc *Service) CancelRequest(reqID ulid.ULID) error {
return svc.ModifyRequest(reqID, nil) return svc.ModifyRequest(reqID, nil, nil)
} }
func (svc *Service) ClearRequests() { func (svc *Service) ClearRequests() {
svc.mu.Lock() svc.reqMu.Lock()
defer svc.mu.Unlock() defer svc.reqMu.Unlock()
for _, req := range svc.requests { for _, req := range svc.requests {
select { select {
@ -189,47 +215,94 @@ func (svc *Service) ClearRequests() {
} }
} }
// Requests returns a list of pending intercepted requests. It's safe for concurrent use. func (svc *Service) ClearResponses() {
func (svc *Service) Requests() []*http.Request { svc.resMu.Lock()
svc.mu.RLock() defer svc.resMu.Unlock()
defer svc.mu.RUnlock()
for _, res := range svc.responses {
select {
case <-res.done:
case res.ch <- nil:
}
}
}
// Items returns a list of pending items (requests and responses). It's safe for concurrent use.
func (svc *Service) Items() []Item {
svc.reqMu.RLock()
defer svc.reqMu.RUnlock()
svc.resMu.RLock()
defer svc.resMu.RUnlock()
reqIDs := make([]ulid.ULID, 0, len(svc.requests)+len(svc.responses))
ids := make([]ulid.ULID, 0, len(svc.requests))
for id := range svc.requests { for id := range svc.requests {
ids = append(ids, id) reqIDs = append(reqIDs, id)
} }
sort.Sort(RequestIDs(ids)) for id := range svc.responses {
reqIDs = append(reqIDs, id)
reqs := make([]*http.Request, len(ids))
for i, id := range ids {
reqs[i] = svc.requests[id].req
} }
return reqs sort.Sort(RequestIDs(reqIDs))
items := make([]Item, len(reqIDs))
for i, id := range reqIDs {
item := Item{}
if req, ok := svc.requests[id]; ok {
item.Request = req.req
}
if res, ok := svc.responses[id]; ok {
item.Response = res.res
}
items[i] = item
}
return items
} }
func (svc *Service) UpdateSettings(settings Settings) { func (svc *Service) UpdateSettings(settings Settings) {
// When updating from `enabled` -> `disabled`, clear any pending reqs. // When updating from `enabled` -> `disabled`, clear any pending reqs.
if svc.enabled && !settings.Enabled { if svc.enabled && !settings.Enabled {
svc.ClearRequests() svc.ClearRequests()
svc.ClearResponses()
} }
svc.enabled = settings.Enabled svc.enabled = settings.Enabled
svc.reqFilter = settings.RequestFilter svc.reqFilter = settings.RequestFilter
} }
// Request returns an intercepted request by ID. It's safe for concurrent use. // ItemByID returns an intercepted item (request and possible response) by ID. It's safe for concurrent use.
func (svc *Service) RequestByID(id ulid.ULID) (*http.Request, error) { func (svc *Service) ItemByID(id ulid.ULID) (Item, error) {
svc.mu.RLock() svc.reqMu.RLock()
defer svc.mu.RUnlock() defer svc.reqMu.RUnlock()
req, ok := svc.requests[id] svc.resMu.RLock()
if !ok { defer svc.resMu.RUnlock()
return nil, ErrRequestNotFound
item := Item{}
found := false
if req, ok := svc.requests[id]; ok {
item.Request = req.req
found = true
} }
return req.req, nil if res, ok := svc.responses[id]; ok {
item.Response = res.res
found = true
}
if !found {
return Item{}, ErrRequestNotFound
}
return item, nil
} }
func (ids RequestIDs) Len() int { func (ids RequestIDs) Len() int {
@ -243,3 +316,124 @@ func (ids RequestIDs) Less(i, j int) bool {
func (ids RequestIDs) Swap(i, j int) { func (ids RequestIDs) Swap(i, j int) {
ids[i], ids[j] = ids[j], ids[i] ids[i], ids[j] = ids[j], ids[i]
} }
func WithInterceptResponse(ctx context.Context, value bool) context.Context {
return context.WithValue(ctx, interceptResponseKey, value)
}
func ShouldInterceptResponseFromContext(ctx context.Context) (bool, bool) {
shouldIntercept, ok := ctx.Value(interceptResponseKey).(bool)
return shouldIntercept, ok
}
// ResponseModifier is a proxy.ResponseModifyMiddleware for intercepting HTTP responses.
func (svc *Service) ResponseModifier(next proxy.ResponseModifyFunc) proxy.ResponseModifyFunc {
return func(res *http.Response) error {
// This is a blocking operation, that gets unblocked when either a modified response is returned or an error.
//nolint:bodyclose
modifiedRes, err := svc.InterceptResponse(res.Request.Context(), res)
if err != nil {
return fmt.Errorf("failed to intercept response: %w", err)
}
*res = *modifiedRes
return next(res)
}
}
// InterceptResponse adds an HTTP response to an array of pending intercepted responses, alongside channels used for
// sending a cancellation signal and receiving a modified response. It's safe for concurrent use.
func (svc *Service) InterceptResponse(ctx context.Context, res *http.Response) (*http.Response, error) {
reqID, ok := proxy.RequestIDFromContext(ctx)
if !ok {
svc.logger.Errorw("Failed to intercept: context doesn't have an ID.")
return res, nil
}
shouldIntercept, ok := ShouldInterceptResponseFromContext(ctx)
if ok && !shouldIntercept {
// If the related request explicitly disabled response intercept, return the response as-is.
svc.logger.Debugw("Bypassed response interception: related request explicitly disabled response intercept.")
return res, nil
}
if !svc.enabled {
// If the feature is disabled, return the response as-is.
svc.logger.Debugw("Bypassed response interception: feature disabled.")
return res, nil
}
// if svc.reqFilter != nil {
// match, err := MatchRequestFilter(req, svc.reqFilter)
// if err != nil {
// return nil, fmt.Errorf("intercept: failed to match request rules for request (id: %v): %w",
// reqID.String(), err,
// )
// }
// if !match {
// svc.logger.Debugw("Bypassed interception: request rules don't match.")
// return req, nil
// }
// }
ch := make(chan *http.Response)
done := make(chan struct{})
svc.resMu.Lock()
svc.responses[reqID] = Response{
res: res,
ch: ch,
done: done,
}
svc.resMu.Unlock()
// Whatever happens next (modified response returned, or a context cancelled error), any blocked channel senders
// should be unblocked, and the response should be removed from the responses queue.
defer func() {
close(done)
svc.resMu.Lock()
defer svc.resMu.Unlock()
delete(svc.responses, reqID)
}()
select {
case modRes := <-ch:
if modRes == nil {
return nil, ErrRequestAborted
}
return modRes, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// ModifyResponse sends a modified HTTP response to the related channel, or returns ErrRequestDone when the related
// request was cancelled. It's safe for concurrent use.
func (svc *Service) ModifyResponse(reqID ulid.ULID, modRes *http.Response) error {
svc.resMu.RLock()
res, ok := svc.responses[reqID]
svc.resMu.RUnlock()
if !ok {
return ErrRequestNotFound
}
if modRes != nil {
modRes.Request = res.res.Request
}
select {
case <-res.done:
return ErrRequestDone
case res.ch <- modRes:
return nil
}
}
// CancelResponse ensures an intercepted response is dropped.
func (svc *Service) CancelResponse(reqID ulid.ULID) error {
return svc.ModifyResponse(reqID, nil)
}

View File

@ -34,7 +34,7 @@ func TestRequestModifier(t *testing.T) {
reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy) reqID := ulid.MustNew(ulid.Timestamp(time.Now()), ulidEntropy)
err := svc.ModifyRequest(reqID, nil) err := svc.ModifyRequest(reqID, nil, nil)
if !errors.Is(err, intercept.ErrRequestNotFound) { if !errors.Is(err, intercept.ErrRequestNotFound) {
t.Fatalf("expected `intercept.ErrRequestNotFound`, got: %v", err) t.Fatalf("expected `intercept.ErrRequestNotFound`, got: %v", err)
} }
@ -65,7 +65,10 @@ func TestRequestModifier(t *testing.T) {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
cancel() cancel()
err := svc.ModifyRequest(reqID, nil) modReq := req.Clone(req.Context())
modReq.Header.Set("X-Foo", "bar")
err := svc.ModifyRequest(reqID, modReq, nil)
if !errors.Is(err, intercept.ErrRequestDone) { if !errors.Is(err, intercept.ErrRequestDone) {
t.Fatalf("expected `intercept.ErrRequestDone`, got: %v", err) t.Fatalf("expected `intercept.ErrRequestDone`, got: %v", err)
} }
@ -107,7 +110,7 @@ func TestRequestModifier(t *testing.T) {
// array of intercepted reqs. // array of intercepted reqs.
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
err := svc.ModifyRequest(reqID, modReq) err := svc.ModifyRequest(reqID, modReq, nil)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }

View File

@ -13,8 +13,9 @@ import (
"net/http/httputil" "net/http/httputil"
"time" "time"
"github.com/dstotijn/hetty/pkg/log"
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/log"
) )
//nolint:gosec //nolint:gosec
@ -189,9 +190,12 @@ func (p *Proxy) clientTLSConn(conn net.Conn) (*tls.Conn, error) {
} }
func (p *Proxy) errorHandler(w http.ResponseWriter, r *http.Request, err error) { func (p *Proxy) errorHandler(w http.ResponseWriter, r *http.Request, err error) {
if !errors.Is(err, context.Canceled) { switch {
case !errors.Is(err, context.Canceled):
p.logger.Errorw("Failed to proxy request.", p.logger.Errorw("Failed to proxy request.",
"error", err) "error", err)
case errors.Is(err, context.Canceled):
p.logger.Debugw("Proxy request was cancelled.")
} }
w.WriteHeader(http.StatusBadGateway) w.WriteHeader(http.StatusBadGateway)