Skip to content

Commit 3b41424

Browse files
committed
init
1 parent da6f8e8 commit 3b41424

File tree

6 files changed

+487
-190
lines changed

6 files changed

+487
-190
lines changed

.idea/gradle.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Android HTTP Server
2+
3+
A sample Android application that demonstrates how to run an embedded HTTP server inside an Android app using Ktor CIO engine.
4+
5+
## Features
6+
7+
- Embedded HTTP server running on Android device
8+
- RESTful API with status endpoint
9+
- Auto-generated Swagger UI documentation
10+
- Static file serving from assets
11+
- CORS support
12+
- Custom error handling
13+
- Foreground service for background operation
14+
- Real-time server status updates
15+
16+
## Tech Stack
17+
18+
- **Kotlin** - Primary programming language
19+
- **Ktor CIO** - Embedded HTTP server engine
20+
- **Jetpack Compose** - Modern Android UI toolkit
21+
- **Kotlinx Serialization** - JSON serialization
22+
- **OpenAPI/Swagger** - API documentation
23+
24+
## Dependencies
25+
26+
The project uses the following key dependencies:
27+
28+
### Project level build.gradle.kts
29+
```kotlin
30+
plugins {
31+
// add for serialization support (optional)
32+
id ("org.jetbrains.kotlin.plugin.serialization") version "2.2.10"
33+
}
34+
35+
```
36+
37+
### Ktor Server Components
38+
```kotlin
39+
implementation("io.ktor:ktor-server-cio:3.2.3")
40+
implementation("io.ktor:ktor-server-core:3.2.3")
41+
implementation("io.ktor:ktor-server-content-negotiation:3.2.3") // (optional)
42+
implementation("io.ktor:ktor-server-call-logging:3.2.3") // (optional)
43+
implementation("io.ktor:ktor-server-cors:3.2.3") // (optional)
44+
implementation("io.ktor:ktor-server-status-pages:3.2.3") // (optional)
45+
implementation("io.ktor:ktor-serialization-kotlinx-json:3.2.3") // (optional)
46+
```
47+
48+
### OpenAPI and Swagger (optional)
49+
```kotlin
50+
implementation("io.github.smiley4:ktor-openapi:5.2.0")
51+
implementation("io.github.smiley4:ktor-swagger-ui:5.2.0")
52+
```
53+
## Required Permissions
54+
55+
Add these permissions to your `AndroidManifest.xml`:
56+
57+
```xml
58+
<uses-permission android:name="android.permission.INTERNET" />
59+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
60+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
61+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
62+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
63+
```
64+
65+
## Service Configuration
66+
67+
Register the HTTP server service in your manifest:
68+
69+
```xml
70+
<service
71+
android:name=".HttpServerService"
72+
android:enabled="true"
73+
android:exported="false"
74+
android:foregroundServiceType="specialUse" />
75+
```
76+
77+
## Project Structure
78+
79+
```
80+
app/src/main/java/com/dihax/androidhttpserver/
81+
├── MainActivity.kt # Main activity with Compose UI
82+
├── HttpServerService.kt # Foreground service for HTTP server
83+
├── MyApplication.kt # Application class
84+
└── server/
85+
├── Server.kt # Ktor server configuration
86+
├── Models.kt # Data models and API responses
87+
└── Utils.kt # Utility functions
88+
89+
app/src/main/assets/web/
90+
├── index.html # Landing page
91+
└── style.css # Styles for web interface
92+
```
93+
94+
## Key Components
95+
96+
### 1. HTTP Server Service (HttpServerService.kt)
97+
98+
Manages the Ktor server lifecycle as a foreground service:
99+
100+
- Starts/stops the HTTP server on demand
101+
- Runs as foreground service with notifications
102+
- Communicates server status to UI via SharedFlow
103+
- Handles service binding for UI interaction
104+
105+
### 2. Server Configuration (Server.kt)
106+
107+
Configures the Ktor server with:
108+
109+
- **Content Negotiation**: JSON serialization with Kotlinx
110+
- **CORS**: Cross-origin resource sharing
111+
- **Status Pages**: Custom error handling
112+
- **Call Logging**: Request logging for API endpoints
113+
- **OpenAPI**: Auto-generated API documentation
114+
- **Static Files**: Serves files from assets/web directory
115+
116+
### 3. API Endpoints
117+
118+
- `GET /api/status` - Health check endpoint
119+
- `GET /api/json` - OpenAPI JSON specification
120+
- `GET /api/swagger` - Swagger UI interface
121+
- `GET /` - Redirects to index.html
122+
- `GET /{file...}` - Static file serving
123+
124+
## Installation and Setup
125+
126+
1. **Clone the repository**
127+
```bash
128+
git clone <repository-url>
129+
cd android-http-server
130+
```
131+
132+
2. **Open in Android Studio**
133+
- Import the project in Android Studio
134+
- Sync Gradle files
135+
136+
3. **Build and run**
137+
- Connect Android device or start emulator
138+
- Build and install the app
139+
140+
## Usage
141+
142+
1. **Grant Permissions**
143+
- Open the app
144+
- Tap "Grant Permissions" to allow notifications
145+
146+
2. **Start Server**
147+
- Enter desired port number (default: 8080)
148+
- Tap "Start Server"
149+
- Server runs in background with notification
150+
151+
3. **Access Server**
152+
- Copy the server address shown in the app
153+
- Open in any web browser on the same network
154+
- Access API documentation at `/api/swagger`
155+
156+
4. **Stop Server**
157+
- Tap "Stop Server" when finished
158+
159+
## Network Access
160+
161+
The server binds to all network interfaces, making it accessible from:
162+
- The device itself (localhost)
163+
- Other devices on the same WiFi network
164+
- Any client that can reach the device's IP address
165+
166+
## API Documentation
167+
168+
When the server is running, visit `/api/swagger` for interactive API documentation generated from OpenAPI specifications.
169+
170+
## Building for Production
171+
172+
The app includes proper resource exclusions for Ktor OpenAPI library to ensure successful builds:
173+
174+
```kotlin
175+
packaging {
176+
resources {
177+
excludes += arrayOf(
178+
"META-INF/ASL-2.0.txt",
179+
"draftv4/schema",
180+
"META-INF/DEPENDENCIES",
181+
// ... other exclusions
182+
)
183+
}
184+
}
185+
```
186+
187+
## Security Considerations
188+
189+
- Server runs on local network only
190+
- No authentication implemented (for demo purposes)
191+
- Consider adding authentication for production use
192+
- Firewall rules may affect external access
193+
194+
## Requirements
195+
196+
- Android API Level 24+ (Android 7.0)
197+
- Network connectivity (WiFi or mobile data)
198+
- Storage for temporary files (if needed)
199+
200+
## Contributing
201+
202+
This is a sample project demonstrating embedded HTTP server capabilities in Android. Feel free to fork and modify for your specific use case.
203+
204+
## License
205+
206+
This project is provided as-is for educational and demonstration purposes.

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@
3434
android:name=".HttpServerService"
3535
android:enabled="true"
3636
android:exported="false"
37-
android:foregroundServiceType="specialUse"
38-
/>
37+
android:foregroundServiceType="specialUse" />
3938
</application>
4039

4140
</manifest>

app/src/main/assets/web/index.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
5+
<meta content="width=device-width, initial-scale=1.0" name="viewport">
66
<title>Android HTTP Server</title>
7-
<link rel="stylesheet" href="style.css">
7+
<link href="style.css" rel="stylesheet">
88
</head>
99
<body>
10-
<div class="container">
11-
<h1>Android HTTP Server</h1>
12-
13-
<div class="endpoints">
14-
<a href="/api/json" class="endpoint">JSON</a>
15-
<a href="/api/swagger" class="endpoint">Swagger</a>
16-
</div>
10+
<div class="container">
11+
<h1>Android HTTP Server</h1>
12+
13+
<div class="endpoints">
14+
<a class="endpoint" href="/api/json">JSON</a>
15+
<a class="endpoint" href="/api/swagger">Swagger</a>
1716
</div>
17+
</div>
1818
</body>
1919
</html>

app/src/main/java/com/dihax/androidhttpserver/HttpServerService.kt

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.app.NotificationChannel
44
import android.app.NotificationManager
55
import android.app.PendingIntent
66
import android.app.Service
7-
import android.content.Context
87
import android.content.Intent
98
import android.os.Binder
109
import android.os.Build
@@ -13,6 +12,13 @@ import androidx.core.app.NotificationCompat
1312
import com.dihax.androidhttpserver.server.CIOEmbeddedServer
1413
import com.dihax.androidhttpserver.server.buildServer
1514
import com.dihax.androidhttpserver.server.getLocalIpAddress
15+
import kotlinx.coroutines.flow.MutableSharedFlow
16+
import kotlinx.coroutines.flow.SharedFlow
17+
import kotlinx.coroutines.flow.asSharedFlow
18+
import kotlinx.coroutines.CoroutineScope
19+
import kotlinx.coroutines.SupervisorJob
20+
import kotlinx.coroutines.Dispatchers
21+
import kotlinx.coroutines.launch
1622

1723
class HttpServerService : Service() {
1824

@@ -27,7 +33,12 @@ class HttpServerService : Service() {
2733
private val binder = LocalBinder()
2834
private var server: CIOEmbeddedServer? = null
2935
private var currentPort = 8080
30-
private var isRunning = false
36+
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
37+
38+
private val _isRunning = MutableSharedFlow<Boolean>(replay = 1)
39+
val isRunning: SharedFlow<Boolean> = _isRunning.asSharedFlow()
40+
41+
private var currentRunningState = false
3142

3243
inner class LocalBinder : Binder() {
3344
fun getService(): HttpServerService = this@HttpServerService
@@ -38,6 +49,9 @@ class HttpServerService : Service() {
3849
override fun onCreate() {
3950
super.onCreate()
4051
createNotificationChannel()
52+
serviceScope.launch {
53+
_isRunning.emit(false)
54+
}
4155
}
4256

4357
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -61,32 +75,37 @@ class HttpServerService : Service() {
6175
currentPort = port
6276
server = buildServer(port)
6377
server?.start(wait = false)
64-
isRunning = true
65-
78+
currentRunningState = true
79+
serviceScope.launch {
80+
_isRunning.emit(true)
81+
}
82+
6683
try {
6784
showNotification("Server running on http://${getLocalIpAddress()}:$port")
68-
} catch (e: Exception) {
85+
} catch (_: Exception) {
6986
}
7087
} catch (e: Exception) {
71-
isRunning = false
88+
currentRunningState = false
89+
serviceScope.launch {
90+
_isRunning.emit(false)
91+
}
7292
try {
7393
showNotification("Failed to start server: ${e.message}")
74-
} catch (notificationError: Exception) {
94+
} catch (_: Exception) {
7595
}
7696
}
7797
}
7898

7999
private fun stopServer() {
80100
server?.stop(1000, 2000)
81101
server = null
82-
isRunning = false
102+
currentRunningState = false
103+
serviceScope.launch {
104+
_isRunning.emit(false)
105+
}
83106
showNotification("Server stopped")
84107
}
85108

86-
fun isServerRunning(): Boolean = isRunning
87-
88-
fun getServerUrl(): String = "http://${getLocalIpAddress()}:$currentPort"
89-
90109
private fun createNotificationChannel() {
91110
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
92111
val channel = NotificationChannel(
@@ -95,7 +114,7 @@ class HttpServerService : Service() {
95114
NotificationManager.IMPORTANCE_LOW
96115
)
97116
val notificationManager =
98-
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
117+
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
99118
notificationManager.createNotificationChannel(channel)
100119
}
101120
}
@@ -112,21 +131,23 @@ class HttpServerService : Service() {
112131
.setContentText(message)
113132
.setSmallIcon(R.drawable.ic_launcher_foreground)
114133
.setContentIntent(pendingIntent)
115-
.setOngoing(isRunning)
134+
.setOngoing(currentRunningState)
116135
.build()
117136

118137
try {
119-
if (isRunning) {
138+
if (currentRunningState) {
120139
startForeground(NOTIFICATION_ID, notification)
121140
} else {
122-
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
141+
val notificationManager =
142+
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
123143
notificationManager.notify(NOTIFICATION_ID, notification)
124144
}
125-
} catch (e: Exception) {
145+
} catch (_: Exception) {
126146
try {
127-
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
147+
val notificationManager =
148+
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
128149
notificationManager.notify(NOTIFICATION_ID, notification)
129-
} catch (e2: Exception) {
150+
} catch (_: Exception) {
130151
}
131152
}
132153
}

0 commit comments

Comments
 (0)