What is the correct way to send back cache headers for static images being served by Ktor?
I have the following Ktor setup:
In my main:
embeddedServer(
Netty,
watchPaths = listOf("module"),
module = Application::module,
port = if (ENV.env == LOCAL) {
8080
} else {
80
}
).apply {
start(wait = true)
}
and then outside the main:
fun Application.module() {
if (ENV.env != LOCAL) {
install(ForwardedHeaderSupport)
install(XForwardedHeaderSupport)
install(HttpsRedirect)
}
install(CachingHeaders) {
options { outgoingContent ->
when (outgoingContent.contentType?.withoutParameters()) {
ContentType.Image.Any -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 30 * 24 * 60 * 60))
else -> null
}
}
}
install(Compression) {
gzip {
priority = 1.0
}
deflate {
priority = 10.0
minimumSize(1024) // condition
}
}
routing {
static("/js/") {
resources("/js/")
}
static("/css/") {
resources("/css/")
}
static("/favicons") {
resources("/favicons/")
}
static("/img/") {
resources("/static/img/")
resources("/static/images/")
resources("/background/")
resources("/logos/")
resources("/icons/")
}
}
}
The images however are coming back with no caching headers, any ideas?
Update:
Changing ContentType.Image.Any to ContentType.Image.JPEG seems to work. Looking at the source code of Image, it seems to map to ContentType(image, *) but is not matching any image type at all.
install(CachingHeaders) {
options { outgoingContent ->
when (outgoingContent.contentType?.withoutParameters()) {
ContentType.Image.JPEG -> CachingOptions(
cacheControl = CacheControl.MaxAge(
mustRevalidate = false,
maxAgeSeconds = 30 * 24 * 60 * 60,
visibility = CacheControl.Visibility.Public
)
)
else -> null
}
}
}
Filed a bug in the meantime:
https://github.com/ktorio/ktor/issues/1366
Turns out that a standard eqauls check is being done on * instead of the actual file type, so using match instead sorts that problem out:
install(CachingHeaders) {
options { outgoingContent ->
if (outgoingContent.contentType?.withoutParameters()?.match(ContentType.Image.Any) == true) {
CachingOptions(
cacheControl = CacheControl.MaxAge(
mustRevalidate = false,
maxAgeSeconds = 6 * 30 * 24 * 60 * 60,
visibility = CacheControl.Visibility.Public
)
)
} else {
null
}
}
}
You can set caching headers by content type using: https://ktor.io/servers/features/caching-headers.html
Related
I'm trying to learn how to use Dome and Wren. To do this I've started working on a simple Flappy Bird clone.
The problem I'm having is that I get the error message
Bird does not implement bird_idle
It then points to line 63 in my main class which is shown below:
class Main {
resources=(value){ _resources = value }
bird=(value){ _bird = value }
construct new() {
_resources = Resources.new()
_bird = Bird.new(_resources.birdIdle, _resources.birdFlap, _resources.obstacleTile.width * 2, _resources.obstacleTile.height * 4, 10, 4)
}
init() {}
update() {}
draw(alpha) {
Canvas.cls()
Canvas.draw(_bird.bird_idle, _bird.center.x, _bird.center.y) <-- this is line 63
}
}
var Game = Main.new()
Since I'm new to Wren I don't quite understand what this means seeing as if you look at my bird class below, I should be implementing bird_idle. So what am I doing wrong?
class Bird {
bird_idle=(value){ _bird_idle = value } <-- Right?
bird_flap=(value){ _bird_flap = value }
center=(value){ _center = value }
gravity=(value){ _gravity = value }
force=(value){ _force = value }
velocity=(value){ _velocity = value }
velocityLimit=(value){ _velocityLimit = value }
isGameRunning=(value){ _isGameRunning = value }
construct new(idle, flap, horizontalPosition, verticalPosition, drag, jumpForce) {
_bird_idle = idle
_bird_flap = flap
_center = Vector.new(horizontalPosition + (idle.width * 0.5), verticalPosition + (idle.height*0.5))
_gravity = drag
_force = jumpForce
_velocityLimit = 1000
_isGameRunning = true
}
jump() {
_velocity = -_force
}
isTouchingCeiling() {
var birdTop = _center.y - (_bird_idle.height * 0.5)
if(birdTop < 0) {
return true
}
return false
}
isTouchingGround() {
var birdBottom = _center.y + (_bird_idle.height * 0.5)
if(birdBottom > height) {
return true
}
return false
}
}
You forgot to add the getter:
class Bird {
construct new(idle) {
_bird_idle = idle
}
bird_idle=(value){_bird_idle = value }
bird_idle{_bird_idle} // You need this if you want to access the field _bird_idle.
}
var my_bird = Bird.new("Idle")
my_bird.bird_idle = "Not Idle"
System.print(my_bird.bird_idle) // Output: Not Idle
I have a need to update the user profile switch
ViewModel
class ProfileViewModel : BaseViewModel() {
var greet = mutableStateOf(user.pushSetting.greet)
var message = mutableStateOf(user.pushSetting.message)
var messageDetails = mutableStateOf(user.pushSetting.messageDetails)
var follow = mutableStateOf(user.pushSetting)
var like = mutableStateOf(user.pushSetting.like)
var comment = mutableStateOf(user.pushSetting.comment)
fun updateUser() {
println("--")
}
}
2.Composable
#Composable
fun SettingCard(viewModel: ProfileViewModel) {
Lists {
Section {
TextRow(text = "手机号码") { }
TextRow(text = "修改密码", line = false) { }
}
Section {
SwitchRow(text = "新好友通知", checkedState = viewModel.greet)
SwitchRow(text = "新消息通知", checkedState = viewModel.message)
SwitchRow(text = "消息显示详细", line = false, checkedState = viewModel.messageDetails)
}
}
}
3.SwitchRow
#Composable
fun SwitchRow(text: String, line: Boolean = true, checkedState: MutableState<Boolean>) {
ListItem(
text = { Text(text) },
trailing = {
Switch(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
colors = SwitchDefaults.colors(checkedThumbColor = MaterialTheme.colors.primary)
)
}
)
}
How can I observe the change of the switch and call updateUser() in ViewModel
I know this is a way, but it is not ideal. The network update will be called every time it is initialized. Is there a better solution?
LaunchedEffect(viewModel.greet) {
viewModel.updateUser()
}
The best solution for this would be to have unidirectional flow with SwitchRow with a lambda as #Codecameo advised.
But if you want to observe a MutableState inside your Viewmodel you can use snapshotFlows as
var greet: MutableState<Boolean> = mutableStateOf(user.pushSetting.greet)
init {
snapshotFlow { greet.value }
.onEach {
updateUser()
}
.launchIn(viewModelScope)
//...
}
Create a Flow from observable Snapshot state. (e.g. state holders
returned by mutableStateOf.) snapshotFlow creates a Flow that runs
block when collected and emits the result, recording any snapshot
state that was accessed. While collection continues, if a new Snapshot
is applied that changes state accessed by block, the flow will run
block again, re-recording the snapshot state that was accessed. If the
result of block is not equal to the previous result, the flow will
emit that new result. (This behavior is similar to that of
Flow.distinctUntilChanged.) Collection will continue indefinitely
unless it is explicitly cancelled or limited by the use of other Flow
operators.
Add a callback lamba in SwitchRow and call it upon any state change
#Composable
fun SettingCard(viewModel: ProfileViewModel) {
Lists {
Section {
TextRow(text = "手机号码") { }
TextRow(text = "修改密码", line = false) { }
}
Section {
SwitchRow(text = "新好友通知", checkedState = viewModel.greet) {
viewModel.updateUser()
}
SwitchRow(text = "新消息通知", checkedState = viewModel.message) {
viewModel.updateUser()
}
SwitchRow(text = "消息显示详细", line = false, checkedState = viewModel.messageDetails) {
viewModel.updateUser()
}
}
}
}
#Composable
fun SwitchRow(
text: String,
line: Boolean = true,
checkedState: MutableState<Boolean>,
onChange: (Boolean) -> Unit
) {
ListItem(
text = { Text(text) },
trailing = {
Switch(
checked = checkedState.value,
onCheckedChange = {
onChange(it)
checkedState.value = it
},
colors = SwitchDefaults.colors(checkedThumbColor = MaterialTheme.colors.primary)
)
}
)
}
Another approach:
You can keep MutableStateFlow<T> in your viewmodel and start observing it in init method and send a value to it from SwitchRow, like viewModel.stateFlow.value = value.
Remember, MutableStateFlow will only trigger in the value changes. If you set same value twice it will discard second value and will execute for first one.
val stateFlow = MutableStateFlow<Boolean?>(null)
init {
stateFlow
.filterNotNull()
.onEach { updateUser() }
.launchIn(viewModelScope)
}
In switchRow
viewmodel.stateFlow.value = !(viewmodel.stateFlow.value?: false)
This could be one potential solution. You can implement it in your convenient way.
I have updated the app.ini file but somewhere someone wrote this code to have used cache. How can I clear the cache?
static $configuration;
if (!$configuration) {
if (Zend_Registry::isRegistered("configuration")) {
$configuration = Zend_Registry::get("configuration");
}
else {
$cache = self::getCache();
if (!$configuration = $cache->load("configuration")) {
$configuration = new Zend_Config_Ini(APPLICATION_PATH . "/config/app.ini", APPLICATION_ENVIRONMENT);
$cache->save($configuration, "configuration");
}
Zend_Registry::set("configuration", $configuration);
}
}
I got the following error, when I run the "vagrant up".
Error: Evaluation Error: Empty string title at 0. Title strings must
have a length greater than zero. at
/tmp/vagrant-puppet/manifests-75f35e3bc7e32744860c4bb229c88812/Firewall.pp:68:37
on node
Please help me, what is the fix for this issue?
Let me know, if you need any other source feed for this.
Here is the code in the Firewall.pp:
class puphpet_firewall ( $firewall, $vm ) {
Firewall {
before => Class['puphpet::firewall::post'],
require => Class['puphpet::firewall::pre'], }
class { ['puphpet::firewall::pre', 'puphpet::firewall::post', 'firewall']: }
# config file could contain no rules key $rules = array_true($firewall, 'rules') ? {
true => $firewall['rules'],
default => { } }
each( $rules ) |$key, $rule| {
if is_string($rule['port']) {
$ports = [$rule['port']]
} else {
$ports = $rule['port']
}
each( $ports ) |$port| {
if ! defined(Puphpet::Firewall::Port["${port}"]) {
if has_key($rule, 'priority') {
$priority = $rule['priority']
} else {
$priority = 100
}
puphpet::firewall::port { "${port}":
protocol => $rule['proto'],
priority => $priority,
action => $rule['action'],
}
}
} }
# Opens up SSH port defined in `vagrantfile-*` section if has_key($vm, 'ssh') and has_key($vm['ssh'], 'port') {
$vm_ssh_port = array_true($vm['ssh'], 'port') ? {
true => $vm['ssh']['port'],
false => 22,
} } else {
$vm_ssh_port = 22 }
if ! defined(Puphpet::Firewall::Port["${vm_ssh_port}"]) {
puphpet::firewall::port { "${vm_ssh_port}": } }
# Opens up forwarded ports on locale machines; remote servers won't have these keys if array_true($vm['vm']['provider'], 'local') {
each( $vm['vm']['provider']['local']['machines'] ) |$mId, $machine| {
# config file could contain no forwarded ports
$forwarded_ports = array_true($machine['network'], 'forwarded_port') ? {
true => $machine['network']['forwarded_port'],
default => { }
}
each( $forwarded_ports ) |$pId, $port| {
if ! defined(Puphpet::Firewall::Port["${port['guest']}"]) {
puphpet::firewall::port { "${port['guest']}": }
}
}
} }
}
Thanks!
I've seen this error when using variables in resource titles that sometimes haven't been set.
Take this line for example... if the hash port does not contain the key 'guest' then the title of resource will be empty and result in error:
puphpet::firewall::port { "${port['guest']}": }
HTTPSecurity.swift:124:22: Cannot invoke 'SecPolicyCreateSSL' with an argument list of type '(Bool, String?)'
I'm getting the above error when trying to build a project containing this code:
public func isValid(trust: SecTrustRef, domain: String?) -> Bool {
var tries = 0
while(!self.isReady) {
usleep(1000)
tries += 1
if tries > 5 {
return false //doesn't appear it is going to ever be ready...
}
}
var policy: SecPolicyRef
if self.validatedDN {
policy = SecPolicyCreateSSL(true, domain)
} else {
policy = SecPolicyCreateBasicX509()
}
SecTrustSetPolicies(trust,policy)
if self.usePublicKeys {
if let keys = self.pubKeys {
var trustedCount = 0
let serverPubKeys = publicKeyChainForTrust(trust)
for serverKey in serverPubKeys as [AnyObject] {
for key in keys as [AnyObject] {
if serverKey.isEqual(key) {
trustedCount++
break
}
}
}
if trustedCount == serverPubKeys.count {
return true
}
}
} else if let certs = self.certificates {
let serverCerts = certificateChainForTrust(trust)
var collect = Array<SecCertificate>()
for cert in certs {
if let c = SecCertificateCreateWithData(nil,cert) {
collect.append(c)
}
}
SecTrustSetAnchorCertificates(trust,collect)
var result: SecTrustResultType = 0
SecTrustEvaluate(trust,&result)
let r = Int(result)
if r == kSecTrustResultUnspecified || r == kSecTrustResultProceed {
var trustedCount = 0
for serverCert in serverCerts {
for cert in certs {
if cert == serverCert {
trustedCount++
break
}
}
}
if trustedCount == serverCerts.count {
return true
}
}
}
return false
}
This code is from:
https://github.com/mpclarkson/SwiftHTTP/blob/swift-2/HTTPSecurity.swift#L124
Method using old fashion Boolean which is in fact UInt8. Method also use CFString and not String.
Easy solution is following:
policy = SecPolicyCreateSSL(1, domain as CFString)
More sophisticated solution is use extension for Bool:
extension Bool {
var booleanValue : Boolean {
return self ? 1 : 0
}
init(booleanValue : Boolean) {
self = booleanValue == 0 ? false : true
}
}
Then you can use:
policy = SecPolicyCreateSSL(true.booleanValue, domain as CFString)