Created
November 24, 2025 18:37
-
-
Save sunmeat/6bdb73868880849e46048cc6c1b0a3bb to your computer and use it in GitHub Desktop.
spring boot + http requests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| HibernateApplication.java: | |
| package site.sunmeat.hibernate; | |
| import java.awt.Desktop; | |
| import java.net.URI; | |
| import org.springframework.boot.SpringApplication; | |
| import org.springframework.boot.autoconfigure.SpringBootApplication; | |
| import org.springframework.boot.context.event.ApplicationReadyEvent; | |
| import org.springframework.context.annotation.Bean; | |
| import org.springframework.context.event.EventListener; | |
| import org.springframework.stereotype.Component; | |
| import org.springframework.web.reactive.function.client.WebClient; | |
| @SpringBootApplication | |
| public class HibernateApplication { | |
| public static void main(String[] args) { | |
| SpringApplication.run(HibernateApplication.class, args); | |
| } | |
| @Bean | |
| WebClient.Builder webClientBuilder() { | |
| return WebClient.builder(); // спрінгу буде потрібен білдер для веб-клієнта, без цього не створиться об'єкт і запити не відправляться | |
| } | |
| } | |
| @Component | |
| class BrowserLauncher { | |
| @EventListener(ApplicationReadyEvent.class) | |
| public void launchBrowser() { | |
| System.setProperty("java.awt.headless", "false"); | |
| Desktop desktop = Desktop.getDesktop(); | |
| try { | |
| desktop.browse(new URI("http://localhost:8080")); | |
| } catch (Exception e) { | |
| } | |
| } | |
| } | |
| =================================================================================================================== | |
| WebController.java: | |
| package site.sunmeat.hibernate; | |
| import org.springframework.http.MediaType; | |
| import org.springframework.stereotype.Controller; | |
| import org.springframework.web.bind.annotation.*; | |
| import org.springframework.web.reactive.function.client.WebClient; | |
| import reactor.core.publisher.Mono; | |
| import java.util.*; | |
| @Controller | |
| public class WebController { | |
| private final WebClient webClient; | |
| public WebController(WebClient.Builder webClientBuilder) { | |
| this.webClient = webClientBuilder.build(); | |
| } | |
| @GetMapping("/") | |
| public String index() { | |
| return "index"; | |
| } | |
| @GetMapping("/api/text") | |
| @ResponseBody | |
| public Mono<Map<String, String>> getText() { | |
| String url = "http://sunmeat.atwebpages.com/volley/plain.txt"; | |
| return webClient.get() | |
| .uri(url) | |
| .retrieve() | |
| .bodyToMono(String.class) | |
| .map(content -> Map.of( | |
| "title", "Текстовий файл (plain.txt)", | |
| "content", content | |
| )) | |
| .onErrorReturn(Map.of("title", "Помилка", "content", "Не вдалося завантажити текстовий файл")); | |
| } | |
| @GetMapping("/api/image") | |
| @ResponseBody | |
| public Mono<Map<String, String>> getImage() { | |
| String url = "https://raw.githubusercontent.com/sunmeat/gallery/main/MyApplication/cats/cat (1).jpg"; | |
| return webClient.get() | |
| .uri(url) | |
| .retrieve() | |
| .bodyToMono(byte[].class) | |
| .map(bytes -> Map.of( | |
| "title", "Зображення котика", | |
| "imageBase64", "data:image/jpeg;base64," + Base64.getEncoder().encodeToString(bytes) | |
| )) | |
| .onErrorReturn(Map.of("title", "Помилка", "content", "Не вдалося завантажити зображення")); | |
| } | |
| @GetMapping("/api/json-object") | |
| @ResponseBody | |
| public Mono<Map<String, String>> getJsonObject() { | |
| String url = "http://sunmeat.atwebpages.com/volley/alex.json"; | |
| return webClient.get() | |
| .uri(url) | |
| .retrieve() | |
| .bodyToMono(String.class) | |
| .map(json -> Map.of( | |
| "title", "JSON-об’єкт (alex.json)", | |
| "content", json | |
| )) | |
| .onErrorReturn(Map.of("title", "Помилка", "content", "Не вдалося завантажити JSON")); | |
| } | |
| @GetMapping("/api/json-array") | |
| @ResponseBody | |
| public Mono<Map<String, String>> getJsonArray() { | |
| String url = "http://sunmeat.atwebpages.com/volley/array.json"; | |
| return webClient.get() | |
| .uri(url) | |
| .retrieve() | |
| .bodyToMono(String.class) | |
| .map(json -> Map.of( | |
| "title", "JSON-масив (array.json)", | |
| "content", json | |
| )) | |
| .onErrorReturn(Map.of("title", "Помилка", "content", "Не вдалося завантажити масив")); | |
| } | |
| @GetMapping("/api/post") | |
| @ResponseBody | |
| public Mono<Map<String, String>> sendPost() { | |
| String url = "http://sunmeat.atwebpages.com/volley/script.php"; | |
| return webClient.post() | |
| .uri(url) | |
| .contentType(MediaType.APPLICATION_FORM_URLENCODED) | |
| .bodyValue("first=10&second=15") | |
| .retrieve() | |
| .bodyToMono(String.class) | |
| .map(response -> Map.of( | |
| "title", "Відповідь від POST-запиту", | |
| "content", response | |
| )) | |
| .onErrorReturn(Map.of("title", "Помилка", "content", "Сервер не відповів")); | |
| } | |
| } | |
| =================================================================================================================== | |
| static / style.css: | |
| @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Playfair+Display:wght@700;900&display=swap'); | |
| :root { | |
| --bg: #0F0A05; | |
| --surface: #3D2B1F; | |
| --coffee: #6F4E37; | |
| --coffee-dark: #5D4037; | |
| --gold: #D4AF37; | |
| --gold-light: #E8C56A; | |
| --cream: #D7CCC8; | |
| --text: #EFEBE9; | |
| --shadow: 0 12px 32px rgba(0,0,0,0.45); | |
| --transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} | |
| html{scroll-behavior:smooth} | |
| body{ | |
| font-family:'Cormorant Garamond',serif; | |
| background:var(--bg); | |
| color:var(--text); | |
| min-height:100vh; | |
| padding:60px 20px; | |
| line-height:1.8; | |
| background:linear-gradient(to bottom,#0F0A05,#1A120B) | |
| } | |
| h1{ | |
| font-family:'Playfair Display',serif; | |
| font-size:clamp(3rem,9vw,6.5rem); | |
| text-align:center; | |
| color:var(--gold); | |
| margin:80px 0 100px; | |
| letter-spacing:2px; | |
| text-shadow:0 4px 12px rgba(0,0,0,0.6); | |
| position:relative | |
| } | |
| h1::after{ | |
| content:''; | |
| position:absolute; | |
| left:50%;bottom:-30px; | |
| transform:translateX(-50%); | |
| width:220px; | |
| height:4px; | |
| background:linear-gradient(90deg,transparent,var(--gold),var(--gold-light),var(--gold),transparent); | |
| border-radius:2px; | |
| box-shadow:0 0 12px rgba(212,175,55,0.6) | |
| } | |
| .container{max-width:1360px;margin:0 auto;padding:0 20px} | |
| .buttons{ | |
| display:grid; | |
| grid-template-columns:repeat(auto-fit,minmax(340px,1fr)); | |
| gap:40px; | |
| margin:80px 0 | |
| } | |
| button{ | |
| position:relative; | |
| background:var(--coffee); | |
| color:var(--cream); | |
| border:none; | |
| padding:38px 24px; | |
| font-family:inherit; | |
| font-size:1.58em; | |
| font-weight:600; | |
| letter-spacing:2.5px; | |
| text-transform:uppercase; | |
| border-radius:18px; | |
| cursor:pointer; | |
| overflow:hidden; | |
| box-shadow:var(--shadow); | |
| transition:var(--transition); | |
| transform:translateZ(0); | |
| will-change:transform | |
| } | |
| button::before{ | |
| content:''; | |
| position:absolute; | |
| inset:0; | |
| background:radial-gradient(circle at var(--x,50%) var(--y,50%), | |
| rgba(212,175,55,0.35) 0%, | |
| rgba(212,175,55,0.15) 40%, | |
| transparent 70%); | |
| opacity:0; | |
| transition:opacity .5s cubic-bezier(0.4,0,0.2,1); | |
| pointer-events:none | |
| } | |
| button:hover::before{ | |
| opacity:1 | |
| } | |
| button:hover{ | |
| transform:translateY(-12px); | |
| box-shadow:0 24px 60px rgba(0,0,0,0.6); | |
| background:var(--coffee-dark) | |
| } | |
| button:active{ | |
| transform:translateY(-6px) | |
| } | |
| .result-card{ | |
| background:var(--surface); | |
| border-radius:22px; | |
| padding:56px; | |
| margin:60px auto; | |
| max-width:1200px; | |
| box-shadow:var(--shadow); | |
| border:1px solid rgba(212,175,55,0.18); | |
| display:none; | |
| animation:fadeIn .9s ease-out | |
| } | |
| @keyframes fadeIn{ | |
| from{opacity:0;transform:translateY(40px)} | |
| to{opacity:1;transform:none} | |
| } | |
| .title{ | |
| font-family:'Playfair Display',serif; | |
| font-size:2.9em; | |
| text-align:center; | |
| color:var(--gold); | |
| margin-bottom:44px; | |
| letter-spacing:2px; | |
| position:relative | |
| } | |
| .title::after{ | |
| content:''; | |
| position:absolute; | |
| left:50%;bottom:-22px; | |
| transform:translateX(-50%); | |
| width:140px; | |
| height:3px; | |
| background:linear-gradient(90deg,transparent,var(--gold),var(--gold-light),var(--gold),transparent); | |
| border-radius:2px; | |
| box-shadow:0 0 10px rgba(212,175,55,0.5) | |
| } | |
| pre{ | |
| background:rgba(15,10,5,0.8); | |
| color:var(--cream); | |
| padding:36px; | |
| border-radius:16px; | |
| font-family:'Courier New',monospace; | |
| font-size:1.12em; | |
| line-height:1.9; | |
| border:1px solid rgba(212,175,55,0.22); | |
| overflow-x:auto; | |
| box-shadow:inset 0 8px 24px rgba(0,0,0,0.5) | |
| } | |
| img{ | |
| max-width:100%; | |
| border-radius:16px; | |
| border:6px double var(--gold); | |
| box-shadow:0 20px 50px rgba(0,0,0,0.65); | |
| display:block; | |
| margin:36px auto; | |
| transition:transform .7s ease | |
| } | |
| img:hover{ | |
| transform:scale(1.03) | |
| } | |
| @media (max-width:768px){ | |
| .buttons{grid-template-columns:1fr;gap:32px} | |
| button{padding:32px 20px;font-size:1.45em} | |
| h1{margin:60px 0 80px} | |
| .result-card{padding:40px} | |
| } | |
| =================================================================================================================== | |
| templates / index.html: | |
| <!DOCTYPE html> | |
| <html lang="uk" xmlns:th="http://www.thymeleaf.org"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>HTTP запити</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Playfair+Display:wght@700;900&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" th:href="@{style.css}" href="style.css"> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="buttons"> | |
| <button onclick="loadData('/api/text')">Завантажити текст</button> | |
| <button onclick="loadData('/api/image')">Показати котика</button> | |
| <button onclick="loadData('/api/json-object')">JSON-об’єкт</button> | |
| <button onclick="loadData('/api/json-array')">JSON-масив</button> | |
| <button onclick="loadData('/api/post')">POST-запит</button> | |
| </div> | |
| <div id="result" class="result-card"> | |
| <div class="title" id="resultTitle">Натисни кнопку</div> | |
| <div id="resultContent"></div> | |
| </div> | |
| </div> | |
| <script> | |
| // курсор на кнопці - центр градієнта | |
| document.querySelectorAll('button').forEach(btn => { | |
| btn.addEventListener('mousemove', e => { | |
| const rect = btn.getBoundingClientRect(); | |
| const x = ((e.clientX - rect.left) / rect.width) * 100 + '%'; | |
| const y = ((e.clientY - rect.top) / rect.height) * 100 + '%'; | |
| btn.style.setProperty('--x', x); | |
| btn.style.setProperty('--y', y); | |
| }); | |
| btn.addEventListener('mouseleave', () => { | |
| btn.style.setProperty('--x', '50%'); | |
| btn.style.setProperty('--y', '50%'); | |
| }); | |
| }); | |
| async function loadData(url) { | |
| const titleEl = document.getElementById('resultTitle'); | |
| const contentEl = document.getElementById('resultContent'); | |
| const card = document.getElementById('result'); | |
| titleEl.textContent = 'Зачекайте...'; | |
| contentEl.innerHTML = ''; | |
| card.style.display = 'block'; | |
| try { | |
| const res = await fetch(url); | |
| const data = await res.json(); | |
| titleEl.textContent = data.title || 'Результат'; | |
| if (data.imageBase64) { | |
| contentEl.innerHTML = `<img src="${data.imageBase64}" alt="Кіт">`; | |
| } else if (data.content) { | |
| contentEl.innerHTML = `<pre>${data.content}</pre>`; | |
| } | |
| } catch (err) { | |
| titleEl.textContent = 'Помилка'; | |
| contentEl.innerHTML = `<pre style="color:#A0522D;">${err.message}</pre>`; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| =================================================================================================================== | |
| build.gradle: | |
| plugins { | |
| id 'java' | |
| id 'org.springframework.boot' version '4.0.0-SNAPSHOT' | |
| id 'io.spring.dependency-management' version '1.1.7' | |
| } | |
| group = 'site.sunmeat' | |
| version = '0.0.1-SNAPSHOT' | |
| description = 'Demo project for Spring Boot' | |
| java { | |
| toolchain { | |
| languageVersion = JavaLanguageVersion.of(25) | |
| } | |
| } | |
| repositories { | |
| mavenCentral() | |
| maven { url = 'https://repo.spring.io/snapshot' } | |
| } | |
| dependencies { | |
| implementation 'org.springframework.boot:spring-boot-starter-web' | |
| implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' | |
| implementation 'org.springframework.boot:spring-boot-starter-webflux' // !!! | |
| } | |
| tasks.named('test') { | |
| useJUnitPlatform() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment