Skip to content

Commit

Permalink
add copying as grpcurl commands
Browse files Browse the repository at this point in the history
  • Loading branch information
sunny-chung committed Dec 3, 2023
1 parent 64935c9 commit cb13ea3
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 17 deletions.
Binary file added doc/_include/grpcurl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions doc/transports/grpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,19 @@ End users input **JSON** payloads and read **JSON** responses.
JSON data is converted to Protobuf for data transmission transparently.

## Updating an API Specification

On clicking the download schema button and the schema is successfully retrieved, it would **replace the one that has the
name `{host}:{port}`** in the same Subproject. If there is none, even if a schema is selected, Hello HTTP would create a
new one with this name. The name can be changed afterwards, and can be managed, as described below.

## Managing API Specifications

Click the pencil (Edit) button next to the current Subproject name. gRPC API specifications can be managed in the
"Edit Subproject" dialog.

![Manage gRPC API Specifications](../manage-grpc-apispec.gif)

## Copy as `grpcurl` commands
A [grpcurl](https://github.com/fullstorydev/grpcurl) command can be copied to send the current request in a command
shell. All service method types are supported. If only response bodies are needed, the verbose option `-v` can be
removed.

![grpcurl](../grpcurl.png)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.sunnychung.application.multiplatform.hellohttp.model.FormUrlEncodedBo
import com.sunnychung.application.multiplatform.hellohttp.model.GraphqlBody
import com.sunnychung.application.multiplatform.hellohttp.model.GraphqlRequestBody
import com.sunnychung.application.multiplatform.hellohttp.model.GrpcApiSpec
import com.sunnychung.application.multiplatform.hellohttp.model.GrpcMethod
import com.sunnychung.application.multiplatform.hellohttp.model.HttpRequest
import com.sunnychung.application.multiplatform.hellohttp.model.MultipartBody
import com.sunnychung.application.multiplatform.hellohttp.model.ProtocolApplication
Expand Down Expand Up @@ -224,15 +225,15 @@ fun HttpRequest.toApacheHttpRequest(): Pair<AsyncRequestProducer, Long> {
return Pair(b.build(), entity?.contentLength ?: 0L)
}

fun UserRequestTemplate.toCurlCommand(exampleId: String, environment: Environment?): String {
fun String.escape(): String {
return replace("\\", "\\\\").replace("\"", "\\\"")
}
private fun String.escape(): String {
return replace("\\", "\\\\").replace("\"", "\\\"")
}

fun String.urlEncoded(): String {
return URLEncoder.encode(this, StandardCharsets.UTF_8)
}
private fun String.urlEncoded(): String {
return URLEncoder.encode(this, StandardCharsets.UTF_8)
}

fun UserRequestTemplate.toCurlCommand(exampleId: String, environment: Environment?): String {
val request = toHttpRequest(exampleId, environment)

val url = request.getResolvedUri().toString()
Expand Down Expand Up @@ -280,3 +281,35 @@ fun UserRequestTemplate.toCurlCommand(exampleId: String, environment: Environmen
}
return curl
}

fun UserRequestTemplate.toGrpcurlCommand(exampleId: String, environment: Environment?, payloadExampleId: String, method: GrpcMethod): String {
val request = toHttpRequest(exampleId, environment)

val uri = request.getResolvedUri()

val currentOS = currentOS()
val newLine = " ${currentOS.commandLineEscapeNewLine}\n "

var cmd = "grpcurl -v"

val isTlsConnection = uri.scheme !in setOf("http", "grpc")
if (isTlsConnection && environment?.sslConfig?.isInsecure == true) {
cmd += "${newLine}-insecure"
} else if (!isTlsConnection) {
cmd += "${newLine}-plaintext"
}
request.headers.forEach {
cmd += "${newLine}-H \"${it.first.escape()}: ${it.second.escape()}\""
}
val payload = if (!method.isClientStreaming) {
(request.body as StringBody).value
} else {
payloadExamples!!.first { it.id == payloadExampleId }.body
}
cmd += "${newLine}-format json"
cmd += "${newLine}-d \"${payload.escape()}\""

cmd += "${newLine}${uri.host}:${uri.port}"
cmd += "${newLine}${grpc!!.service}/${grpc!!.method}"
return cmd
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import com.sunnychung.application.multiplatform.hellohttp.document.RequestCollec
import com.sunnychung.application.multiplatform.hellohttp.document.RequestsDI
import com.sunnychung.application.multiplatform.hellohttp.document.ResponsesDI
import com.sunnychung.application.multiplatform.hellohttp.extension.toCurlCommand
import com.sunnychung.application.multiplatform.hellohttp.extension.toGrpcurlCommand
import com.sunnychung.application.multiplatform.hellohttp.network.ConnectionStatus
import com.sunnychung.application.multiplatform.hellohttp.model.ColourTheme
import com.sunnychung.application.multiplatform.hellohttp.model.Environment
Expand Down Expand Up @@ -475,6 +476,17 @@ fun AppContentView() {
false
}
},
onClickCopyGrpcurl = { payloadExampleId, grpcMethod ->
val cmd = requestNonNull.toGrpcurlCommand(
exampleId = selectedRequestExampleId!!,
environment = selectedEnvironment,
payloadExampleId = payloadExampleId,
method = grpcMethod,
)
log.d { "grpcurl: $cmd" }
clipboardManager.setText(AnnotatedString(cmd))
true
},
onRequestModified = {
log.d { "onRequestModified" }
it?.let { update ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import com.sunnychung.application.multiplatform.hellohttp.model.FileBody
import com.sunnychung.application.multiplatform.hellohttp.model.FormUrlEncodedBody
import com.sunnychung.application.multiplatform.hellohttp.model.GraphqlBody
import com.sunnychung.application.multiplatform.hellohttp.model.GrpcApiSpec
import com.sunnychung.application.multiplatform.hellohttp.model.GrpcMethod
import com.sunnychung.application.multiplatform.hellohttp.model.MultipartBody
import com.sunnychung.application.multiplatform.hellohttp.model.PayloadExample
import com.sunnychung.application.multiplatform.hellohttp.model.ProtocolApplication
Expand Down Expand Up @@ -91,6 +92,7 @@ fun RequestEditorView(
onClickSend: () -> Unit,
onClickCancel: () -> Unit,
onClickCopyCurl: () -> Boolean,
onClickCopyGrpcurl: (selectedPayloadExampleId: String, method: GrpcMethod) -> Boolean,
onRequestModified: (UserRequestTemplate?) -> Unit,
connectionStatus: ConnectionStatus,
onClickConnect: () -> Unit,
Expand Down Expand Up @@ -127,6 +129,7 @@ fun RequestEditorView(
val hasPayloadEditor = (request.application == ProtocolApplication.WebSocket
|| (request.application == ProtocolApplication.Grpc && currentGrpcMethod?.isClientStreaming == true)
)
var selectedPayloadExampleId by remember { mutableStateOf(request.payloadExamples?.firstOrNull()?.id) }

log.d { "RequestEditorView recompose $request" }

Expand Down Expand Up @@ -284,6 +287,14 @@ fun RequestEditorView(
"Copy as cURL command" -> {
isSuccess = onClickCopyCurl()
}
"Copy as grpcurl command" -> {
isSuccess = try {
onClickCopyGrpcurl(selectedPayloadExampleId!!, currentGrpcMethod!!)
} catch (e: Throwable) {
log.d(e) { "Cannot copy grpcurl command" }
false
}
}
}
isSuccess
},
Expand Down Expand Up @@ -597,6 +608,8 @@ fun RequestEditorView(
modifier = Modifier.weight(0.7f).fillMaxWidth(),
request = request,
onRequestModified = onRequestModified,
selectedPayloadExampleId = selectedPayloadExampleId!!,
onSelectExample = { selectedPayloadExampleId = it.id },
hasCompleteButton = request.application == ProtocolApplication.Grpc && currentGrpcMethod?.isClientStreaming == true,
knownVariables = environmentVariableKeys,
onClickSendPayload = onClickSendPayload,
Expand Down Expand Up @@ -1191,6 +1204,8 @@ fun StreamingPayloadEditorView(
editExampleNameViewModel: EditNameViewModel = remember { EditNameViewModel() },
request: UserRequestTemplate,
onRequestModified: (UserRequestTemplate?) -> Unit,
selectedPayloadExampleId: String,
onSelectExample: (PayloadExample) -> Unit,
hasCompleteButton: Boolean,
knownVariables: Set<String>,
onClickSendPayload: (String) -> Unit,
Expand All @@ -1199,16 +1214,13 @@ fun StreamingPayloadEditorView(
) {
val colors = LocalColor.current

var selectedExampleId by remember { mutableStateOf(request.payloadExamples!!.first().id) }
var selectedExample = request.payloadExamples!!.firstOrNull { it.id == selectedExampleId }

fun onSelectExample(example: PayloadExample) {
selectedExampleId = example.id
selectedExample = example
}
var selectedExample = request.payloadExamples!!.firstOrNull { it.id == selectedPayloadExampleId }

if (selectedExample == null) {
onSelectExample(request.payloadExamples.first())
request.payloadExamples.first().let {
onSelectExample(it)
selectedExample = it
}
}

Column(modifier) {
Expand Down

0 comments on commit cb13ea3

Please sign in to comment.