Repository: provectus/kafka-ui
Issue: No URL encoding while getting schemas
Labels: type/bug
good first issue
scope/backend
scope/frontend
status/accepted
status/confirmed

**Describe the bug**
We have subject with '/' sign in it, what causes exception after choosing 'Schema Registry' option. I can see that app requests for:
`GET https://schema-registry.nonprod.ipfdigital.io/subjects/apollo/commons/Header.proto/versions/latest`
so without proper URL encoding, and should be:
`https://schema-registry.nonprod.ipfdigital.io/subjects/apollo%2fcommons%2fHeader.proto/versions/latest`

In version 0.3.x everything works fine, something broke in 0.4.

**Screenshots**
<!--
(If applicable, add screenshots to help explain your problem)
-->

![image](https://user-images.githubusercontent.com/16690739/169000231-d76ad178-8d0a-4d33-840b-c5ccf76fd4de.png)

**Exception**
`{"code":4009,"message":"No such schema apollo/commons/Header.proto with version latest","timestamp":1652860516935,"requestId":"5d9f795f-81176","fieldsErrors":null,"stackTrace":"com.provectus.kafka.ui.exception.SchemaNotFoundException: No such schema apollo/commons/Header.proto with version latest\n\tat com.provectus.kafka.ui.service.SchemaRegistryService.lambda$throwIfNotFoundStatus$20(SchemaRegistryService.java:238)\n\tSuppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: \nError has been observed at the following site(s):\n\t*__checkpoint ⇢ 404 from GET http://schema-registry-service:8081/subjects/apollo/commons/Header.proto/versions/latest [DefaultWebClient]\n\t*__checkpoint ⇢ Handler com.provectus.kafka.ui.controller.SchemasController#getSchemas(String, Integer, Integer, String, ServerWebExchange) [DispatcherHandler]\n\t*__checkpoint ⇢ com.provectus.kafka.ui.config.ReadOnlyModeFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ com.provectus.kafka.ui.config.CustomWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]\n\t*__checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]\n\t*__checkpoint ⇢ HTTP GET \"/api/clusters/nonprod-msk-cluster/schemas\" [ExceptionHandlingWebHandler]\nOriginal Stack Trace:\n\t\tat com.provectus.kafka.ui.service.SchemaRegistryService.lambda$throwIfNotFoundStatus$20(SchemaRegistryService.java:238)\n\t\tat org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec$StatusHandler.apply(DefaultWebClient.java:695)\n\t\tat org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.applyStatusHandlers(DefaultWebClient.java:654)\n\t\tat org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.handleBodyMono(DefaultWebClient.java:623)\n\t\tat org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.lambda$bodyToMono$2(DefaultWebClient.java:541)\n\t\tat reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)\n\t\tat reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)\n\t\tat reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:120)\n\t\tat reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)\n\t\tat reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:200)\n\t\tat reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:200)\n\t\tat reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:200)\n\t\tat reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)\n\t\tat reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2398)\n\t\tat reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onSubscribeInner(MonoFlatMapMany.java:150)\n\t\tat reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onNext(MonoFlatMapMany.java:189)\n\t\tat reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:99)\n\t\tat reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onNext(FluxRetryWhen.java:174)\n\t\tat reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:165)\n\t\tat reactor.netty.http.client.HttpClientConnect$HttpIOHandlerObserver.onStateChange(HttpClientConnect.java:414)\n\t\tat reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:671)\n\t\tat reactor.netty.resources.DefaultPooledConnectionProvider$DisposableAcquire.onStateChange(DefaultPooledConnectionProvider.java:183)\n\t\tat reactor.netty.resources.DefaultPooledConnectionProvider$PooledConnection.onStateChange(DefaultPooledConnectionProvider.java:439)\n\t\tat reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:637)\n\t\tat reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)\n\t\tat io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)\n\t\tat io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)\n\t\tat io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:327)\n\t\tat io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:314)\n\t\tat io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:435)\n\t\tat io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:279)\n\t\tat io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)\n\t\tat io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)\n\t\tat io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)\n\t\tat io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:795)\n\t\tat io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:480)\n\t\tat io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)\n\t\tat io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)\n\t\tat io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)\n\t\tat io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)\n\t\tat java.base/java.lang.Thread.run(Thread.java:830)\n"}`

Issue comments:
Hello there lbalcerek! 👋

Thank you and congratulations 🎉 for opening your very first issue in this project! 💖

In case you want to claim this issue, please comment down below! We will try to get back to you as soon as we can. 👀
Hey, thanks for reaching out. We'll take a look.
Steps to reproduce:
1. `provectuslabs/kafka-ui:0.3.0`, create a schema with name "test/test"
2. Get a list of schemas. It works fine.
3. Do the same with `provectuslabs/kafka-ui:0.4.0`, request to fetch schemas list fails.

Seems the problem is actual on master, newly created Schema with "/" sign in Subject, is not available. See attached screen record please.

https://user-images.githubusercontent.com/104780608/187169410-9f59ad78-6ae2-4cd5-80eb-e1194b01571b.mov


@shubhwip wanna take a look?
> @shubhwip wanna take a look?

This is something i mentioned earlier two times on the frontend PR below :) and also shared a video for exact same problem.
https://github.com/provectus/kafka-ui/pull/2483#issuecomment-1225243434
https://user-images.githubusercontent.com/23444368/186343542-9be868f5-621b-4a2a-9fa5-e49c686465a0.mov
Unfortunately i don't think i have a solution for this :), I tried before already.
> Seems the problem is actual on master, newly created Schema with "/" sign in Subject, is not available. See attached screen record please.
> 
>  testSchema.mov

**Just a note** : If you try to manually replace `/` with `%2F` in the browser tab then it will work however it is not a solution. Just mentioning it here.
> > @shubhwip wanna take a look?
> 
> This is something i mentioned earlier two times on the frontend PR below :) and also shared a video for exact same problem. [#2483 (comment)](https://github.com/provectus/kafka-ui/pull/2483#issuecomment-1225243434) https://user-images.githubusercontent.com/23444368/186343542-9be868f5-621b-4a2a-9fa5-e49c686465a0.mov Unfortunately i don't think i have a solution for this :), I tried before already.

Oh well, sorry, I got a feeling you've fixed this eventually. 

@Kris-K-Dev PTAL https://github.com/provectus/kafka-ui/pull/2483#issuecomment-1225243434
Actual on master: newly created schema with "/" sign is not available

<img width="1621" alt="schema new" src="https://user-images.githubusercontent.com/104780608/190325765-cc0b3947-68bb-4edf-9fbe-f6b2d7600f88.png">

@armenuikafka Can you please share video for steps you followed for reproducing because I just tried from master and it works.
<img width="1440" alt="Screenshot 2022-09-15 at 11 51 39 AM" src="https://user-images.githubusercontent.com/23444368/190331576-a2aaf33d-dba8-4768-a4a1-d329c646dddc.png">

The env this has been checked on is pretty much outdated considering the commit tag on the screenshot :)
@shubhwip there was a problem which is fixed now and everything works as expected !
@shubhwip thank you :)

Pull request review comments:

Patch that resolved the issue:
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/SchemaRegistryService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/SchemaRegistryService.java
index f4c9355804e..92603ba979e 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/SchemaRegistryService.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/SchemaRegistryService.java
@@ -381,7 +381,7 @@ private URI buildUri(InternalSchemaRegistry schemaRegistry, String path, List<St
     final var builder = UriComponentsBuilder
         .fromHttpUrl(schemaRegistry.getUri() + path);
     builder.queryParams(queryParams);
-    return builder.buildAndExpand(uriVariables.toArray()).toUri();
+    return builder.build(uriVariables.toArray());
   }
 
   private Function<ClientResponse, Mono<? extends Throwable>> errorOnSchemaDeleteFailure(String schemaName) {


Test patch:
diff --git a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/SchemaRegistryServiceTests.java b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/SchemaRegistryServiceTests.java
index 190831da98b..60959be0492 100644
--- a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/SchemaRegistryServiceTests.java
+++ b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/SchemaRegistryServiceTests.java
@@ -274,6 +274,21 @@ public void shouldOkWhenCreateNewSchemaThenGetAndUpdateItsCompatibilityLevel() {
         });
   }
 
+  @Test
+  void shouldCreateNewSchemaWhenSubjectIncludesNonAsciiCharacters() {
+    String schema =
+        "{\"subject\":\"test/test\",\"schemaType\":\"JSON\",\"schema\":"
+        + "\"{\\\"type\\\": \\\"string\\\"}\"}";
+
+    webTestClient
+        .post()
+        .uri("/api/clusters/{clusterName}/schemas", LOCAL)
+        .contentType(MediaType.APPLICATION_JSON)
+        .body(BodyInserters.fromValue(schema))
+        .exchange()
+        .expectStatus().isOk();
+  }
+
   private void createNewSubjectAndAssert(String subject) {
     webTestClient
         .post()